the ugly org - primavera sin flores, java sin spring

desu

En la estación equivocada, los árboles olvidaron florecer. El viento, frío como el silencio, se deslizaba entre ramas desnudas, susurrando secretos que nadie escuchaba. El cielo, gris y pesado, miraba hacia abajo, como un espejo donde nadie quería verse.

Caminé solo por la tierra, pisando hojas secas de otoños pasados. En cada crujido, escuchaba un eco lejano, una voz que parecía mía pero no lo era. Me pregunté si la soledad era un castigo o un regalo, si este frío era el abrazo que siempre había evitado.

Al final, bajo ese cielo inmenso y sin colores, entendí que no se puede encontrar la primavera afuera cuando todo lo que florece está dentro.


3
desu
flox init
flox install maven
flox install java
flox activate
mvn archetype:generate -DgroupId=com.github.vrnvu -DartifactId=java-no-spring -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 -DinteractiveMode=false
git init
curl https://www.toptal.com/developers/gitignore/api/java > .gitignore
git add .
git commit -am "init"
git remote add origin https://github.com/vrnvu/java-no-spring.git
git push -u origin main
cd java-no-spring
mvn compile
mvn clean compile exec:java -Dexec.mainClass="com.github.vrnvu.App"
[INFO] --- exec:3.5.0:java (default-cli) @ java-no-spring ---
Hello World!

https://github.com/vrnvu/java-no-spring

En VSCode instalar LSP: https://marketplace.visualstudio.com/items?itemName=redhat.java

public class App {
    private static final Logger logger = Logger.getLogger(App.class.getName());

public static void main(String[] args) throws IOException {
    var port = 8080;
    var server = HttpServer.create(new InetSocketAddress(port), 0);
    server.createContext("/", (exchange) -> {
        exchange.sendResponseHeaders(200, 0);
        exchange.getResponseBody().write("Hello World!".getBytes());
        exchange.getResponseBody().close();
    });
    logger.log(Level.INFO, "Server started on port {0}", port);
    server.start();
}
}
curl localhost:8080
Hello World!%

Que dificil!

Vamos a usar corutinas, +java 21

        server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());

Y si queremos usar un thread pool:

        int nThreads = Runtime.getRuntime().availableProcessors();
        server.setExecutor(Executors.newFixedThreadPool(nThreads));
1
desu

Tenemos un handler, le pasamos todo por parametro, la famosa inyección de dependencias.

En los tests puedes usar mocks facilmente.
Podrias tener metodos por defecto como TodoHandler.defaultHandler() que te inyectase todo.
Podrias tener un SINGLETON!!!! Para el ObjectMapper, pero vamos, que no te lo vas a olvidar y es un parametro al constructor...
No hace falta tener frameworks de DI locos...


Y esto para todo el que haya trabajado en cosas serias es una preciosidad... Si algo falla, manejas la excepcion.
Escribir jsons con Jackson, son 2 lineas de codigo... Cuando la aplicacion crece tansolo necesitas un par de wrappers sobre los errores para tener un mapeo de Excepcion => HTTP Status Code.
Podriamos tener un Result<T, String> o cosas del estilo en lugar de excepciones, pero llegados a este punto, no vas a cambiar a la gente de como se usa Java.

SOLID:

  • Handler: Valida inputs, params, querys, headers, y respuestas.
  • Servicios: Haces cosas, gestionas error con excepciones.
  • Repositorios: Pues hablar con dbs o APIs externas.

No veo mucha dificultad.

1
desu

Diferenciar entre GET, PUT, DELETE es facil.

Una dificultad real es encontrar el path exacto:

Me podria copiar alguna implementacion, es triste que no haya encontrado una solo libreria buena... para algo que usan ABSOLUTAMENTE TODOS los frameworks y librerias de backend no? Dice mucho de la calidad del ecosistema.

https://github.com/javalin/javalin/tree/master/javalin/src/main/java/io/javalin/router/matcher

En lugar de tener una puta libreria que funcione y vaya rapido para todo el mundo, todos re-inventan la rueda de la peor manera posible. Que importa que sea una parte critica del codigo.

Pero vamos, no es que sea un problema muy dificil si todos los Pedros y Manoloskwis de internet se implementan su propio algoritmo.

Con esto ya tendriamos, routing por metodo, routing por path y extraer variables (query params, URI params) y construir una respuesta HTTP.

Hace falta algo mas? Bueno, otro dia sigo añadiendo mas cosas.

nV8x

reducir a que Spring (o JavaEE) solo se usa para DI, es un poco absurdo

1 respuesta
desu

#5 para que se usa spring? añadir bloat? tener un context global generico lleno de beans xq nadie usa los context por packaging que hacen que inicializar un proyecto tenga 120 beans? problemas de resolución de dependencias en runtime? que estos problemas como todo es generico y tengas capas de abstracción no contengan ningun tipo de informacion sobre como resolverse y todo se reduzca a prueba y error? librerias deprecated completamente inutiles para ORM que solo perjudican el rendimiento? conectores malos, poco eficientes y llenos de bugs para cualquier cosa que exista a medio hacer que una vez tratas de hacer mas del hello world todo funcione mal? default hells? config hells? bugs transaccionales en kafka, rabbitmq, o cualquier base de datos que exista xq los defaults estan mal y no se pueden configurar sin re-escribir todos los beans?

he hecho un par de contribuciones a spring y creo que se bastante bien la porqueria que es. con las nuevas corutinas esta completamente muerto, como la mitad de los frameworks de java. si hasta la diferencia entre jetty y netty que era el runtime ahora no vale para nada porque ambos pueden correr con green threads y usar el runtime de jvm.

pero vamos que este hilo no es una critica o analisis de spring, todo el mundo sabe que spring es una porqueria. tansolo esoty haciendo las cosas sin dependencias para que la gente vea los probelmas que surgen o no, y que se necesita y que es bloat.

de momento hemos necesitado:

  • jackson para serlializar/deserializar
  • alguna libreria de path para el routing/mux (no he encontrado)
1
desu

He integrado sqlite y hecho un get, getAll y un put.

La verdad es que aqui la experiencia de java con el tema responses es bastante pobre. Y el descontrol de las checked exception empieza a ser palpable. por que o bien checkeas, para poder gestionarlo, loggear etc o lo dejas petar y lo ignoras...

si lo comparo con el mismo proyecto de todo usando sqlite pero escrito en Go:


Las comparaciones son odiosas. Ademas que el proyecto en Go es mas potente porque ya me esta gestionando cancelar el contexto de la corrutina...

En Rust es mas verboso también, pero nada de esto es runtime, todo se optimiza y la experiencia de desarrollo y usuario es buena. Es imposible que exista un error que no se haya comprobado de manera exhaustiva y verificado en tiempo de compilacion.

No voy a ponerme a explicar la porqueria que es Java y sus excepciones checked/unchecked. En Rust tienes codigo verboso y sabes que todo va genial y perfecto, es imposible cometer un error. En Golang todo esta diseñado para que funcione sin hacerte daño, y el codigo es facil de seguir. En Java, tienes 20 lienas y no sabes si te has dejado una excepcion y el orden importa y si algo peta no te enteraras hasta que es demasiado tarde.

Horrible Java.

1 respuesta
pantocreitor

#7 trabajando con Java pues si, es bastante tocapelotas sobre todo cuando te acostumbras a otras cosas.
Aún así y necesitando más código se puede dejar todo muy bien.

Por otro lado, me mola este devlog, la gente está muy pesada con Spring y parece que no hay nada más cuando con Java vainilla se pueden hacer muchas cosas y de manera mucho más óptima, sin dependencias a cascoporro y demás.

Por curiosidad, con el tema de los hilos solo vas a paralelizar o vas a utilizar locks, semáforos, etc… para trastear?

2 respuestas
desu

#8 Si haces:

 server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());

te arranca un runtime y te ejecutara cada petición en una corrutina, no hace falta mucho mas.

si alguien se fija yo lo tengo desactivado xq no me configura bien el Cursor el java 21... y paso de tener un error en el editor todo el rato. si quiero hacer algo con virtual threads lo activo y lo pruebo a mano. solo es tema de configurar el LSP bien o lo que sea que me falla.

en java 23 estan aun trabajando lo que serian los grupos de corrutinas:

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String> user = scope.fork(() -> findUser());
        Supplier<Integer> order = scope.fork(() -> fetchOrder());

    scope.join()            // Join both subtasks
         .throwIfFailed();  // Propagate errors if any

    // Both subtasks have succeeded, compose their results
    return new Response(user.get(), order.get());
}
}

pero aun no esta, java solo va 15 años tarde respecto a la competencia jeje.

Por curiosidad, con el tema de los hilos solo vas a paralelizar o vas a utilizar locks, semáforos, etc… para trastear?

No tengo nada que paralelizar mas alla de las peticiones, que ya he comentado como hacerlo.

Si que voy a requerir sincronizar mi sqlite, ya que sqlite usa un modelo que debe ir a 1 thread. Supongo que lo hare con un mutex y una referencia global compartida... no es mucho problema la verdad. igualmente sqlite internamente te va a poner el cola... internamente es siempre 1 writer, no puede haber writers concurrentes, aunque tengo que mirar bien que riesgos de poisoning hay en usar mutexes en Java, que hace mucho q no los uso.

Java vainilla se pueden hacer muchas cosas y de manera mucho más óptima, sin dependencias a cascoporro y demás.

Si, el problema es que no hay buenas librerías para las cosas tipicas... todo esta embutido en frameworks para embaucar a la gente... es un ecosistema de garrulos para garrulos.

Si java tuviese un ecosistéma de librerias pequeñas como rust, go y demas... todo el mundo haria vanilla sin duda desde los virtual threads.

Como he comentado si quiero los paths necesito copiar esto de Spring (o similares):

Que si buscas en github encontraras varias personas que lo han sacado para poder usarlo.

Java las librerías de la std lib NO estan bien diseñadas y son muy complejas de usar para lo que son... si o si tienes que invertir en hacerte una pequeña libreria de wrappers... no es mucho trabajo.

Es mas facil de mantener java a pelo, que cualquier proyecto con spring (frameworks similares que luego petan y tienen incompatibilidad).

Yo he mantenido proyectos en java que cada mes tenias 2-3 bugs, solo haciendo upgrade de librerias tipicas (spring kafka, postgresql, lo que sea tipico)... en cambio en Go solo tienes 1 bug cada 6-10 meses... 1 o 2 al año vs los 12 de java... el de go es un breaking bastante obvio además que se arregla en 1 dia, el de java son semanas de profiling y encontrar quien es el fpero.

desu

La verdad es que Java así a pelo, para alguien que sabe lo que hace es fácil y simple. Mucho mas que cualquier librerías http o framework del ecosistema. Estoy contento que Java por fin soporte el runtime nativo y las corrutinas. Si nunca tengo que usar Java para lo que sea ahora puedo hacerlo a pelo y no hay manera de convencerme de lo contrario. Se acabaron las excusas de fperos y razones de "no se programar".

Pero también tengo claro que el 99% de programadores de Java serian completamente incapaces de hacer lo que he hecho esta tarde y entenderlo. Un programador Java no sabe ni lo que es una referencia como para ponerle a pensar sobre threads. Le sacas del hello world de Spring y el Clean Code de un curso de Youtube y no da para más. Asi que Spring no va a desaparecer, incluso ahora que Jetty y Netty y Tomcat van a ser prácticamente iguales los fperos seguirán matandose porque uno tiene una sintaxis y otro tiene otra.

aren-pulid0

Que caña Flox, estoy haciendo la prueba para una empresa y hacen falta algunas herramientas para levantar el entorno, lo recordaba pero ha sido el click perfecto, gracias.

Por cierto, en el catch que haces en Java, no podrias hacer esto?

catch (Exception ex) {
   log(e)
   handler.response(e, statusCode.500)
}

Y si, yo estoy completamente de acuerdo que tener Spring o frameworks monstruosos que te meten el server http y un monton de mierda a la larga acaba pasando factura

1 respuesta
desu

#11 no entiendo a que te refieres, tener una Exception en lugar de hilar fino? Me parece mala practica porque pierdes gestion de errores, para ir bien deberias tener un pattern matching exhaustivo de todos los posibles errores, siempre checked para que sea obligatorio, nada de runtimeexceptions.

si no es eso lo que dices, no entiendo a que te refieres.

1 respuesta
aren-pulid0

#12 hmmm te refieres a que Java no te dice si esa funcion puede sacar excepciones?

con el codigo que he puesto emulas el comportamiento de Rust y Go, pero claro no sabes si la funcion tirara excepciones

1 respuesta
desu

#13 no se que a te refieres, a la funcion que sigue teniendo:

    private void handlePut(HttpExchange exchange) throws IOException 

quitare el IOException metiendo todo en try catches?

1 respuesta
aren-pulid0

#14

private void handleGetTodo(HttpExchange exchange, String id) throws IOException {
        ....
        try {
            var response = objectMapper.writeValueAsBytes(todo.get());
            Handler.response(exchange, response);
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Error", e);
            Handler.statusCode(exchange, 500);
        }
    }

=

?

1 respuesta
desu

#15 No es igual. Podrias tener algo asi:

        try {
            var response = objectMapper.writeValueAsBytes(todo.get());
            Handler.response(exchange, response);
        } catch (Status400Exception e) {
            logger.log(Level.SEVERE, "Error", e);
            Handler.statusCode(exchange, 400);
        } catch (Status500Exception e) {
            logger.log(Level.SEVERE, "Error", e);
            Handler.statusCode(exchange, 400);
        }

Si quieres esconderlo lo metes en el servicio que llamas, pero en algun lugar necesitas mappear los errores.

Con Rust sabes que los has mapeado y comprovado todos, con Go igual, porque es obligatorio.

Con Java no puedes saber si tienes unchecked exceptions fallando, segun el orden en que compruebas las excepciones puede que te entre en un branch u otro, xq polimorfismo y OOP a tope, te generan stack traces, los bloques try/catch no son expresiones...

Volviendo a tu pregunta original si, podria moverlo al service o tener las excepciones a nivel de HttpStatus para mappear directamente mas facil, para que quede mas limpio, pero seguiria teniendo los problemas anteriores. Aparentemente seria parecido pero semanticamente funcionaria muy distinto y java da muchos problemas a la larga.

En este caso, el Handler.Java es el que se encarga de toda la logica de HTTP, no quiero mover este codigo en el servicio, podria tener funciones auxiliares y tal, que mappean excepciones de servicio a excepciones HTTP o construir directamente la response si falla.

Si quieres ser fino, lo suyo seria que tu capa de dominio (como una libreria) devuelva Excepciones custom y no excepciones genericas, igual que cuando usas Thiserror vs Anyhow en Rust. Usariamos thiserror para mappear a errores de dominio, y luego en la capa de http mappear a errores Http para mappear a las response.

Por eso he hecho una TodoException que es estatica de mi TodoService, considero TodoService una libreria, que lo es. Y lo mismo seria para el Repositorio que interactúa por IO con cosas externas.

    public static class TodoException extends Exception {
        public TodoException(String message, Throwable cause) {
            super(message, cause);
        }
    }

Si en lugar de Exception tengo RuntimeException

    public static class TodoException extends RuntimeException {
        public TodoException(String message, Throwable cause) {
            super(message, cause);
        }
    }

En el caller:


private void handlePut(HttpExchange exchange) throws IOException {
    var todo = objectMapper.readValue(exchange.getRequestBody(), Todo.class);
    todoService.insertTodo(todo);
    try {
        Handler.statusCode(exchange, 200);
    } catch (TodoService.TodoException e) {
        logger.log(Level.SEVERE, "Error inserting todo", e.getCause());
        Handler.statusCode(exchange, 500);
    }
}

El insertTodo ya no necesita estar dentro del bloque try/catch, esto es porque es un checked, petaria y no te enteras xq la funcion handlePut tira throws IOException.

Para ir bien deberias tener try/catch en todo.

    private void handlePut(HttpExchange exchange) {
        try {
            var todo = objectMapper.readValue(exchange.getRequestBody(), Todo.class);
            todoService.insertTodo(todo);
            Handler.statusCode(exchange, 200);
        } catch (TodoService.TodoException e) {
            logger.log(Level.SEVERE, "Error inserting todo", e.getCause());
            try {
                Handler.statusCode(exchange, 500);
            } catch (IOException ioe) {
                logger.log(Level.SEVERE, "Error writing response", ioe);
            }
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Error reading request body", e);
            try {
                Handler.statusCode(exchange, 500);
            } catch (IOException ioe) {
                logger.log(Level.SEVERE, "Error writing response", e);
            }
        }
    }

Esto es lo que deberias tener.

Ahora si queremos mover el try/catch y al Handler.statusCode()... De esto:

    public static void statusCode(HttpExchange exchange, int statusCode) throws IOException {
        try (exchange) {
            exchange.sendResponseHeaders(statusCode, 0);
        }
    }

Necesitas esto:

    public static void statusCode(HttpExchange exchange, int statusCode) {
        try {
            exchange.sendResponseHeaders(statusCode, 0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

Pero hay un problema. Vuelves a tirar una unchecked exception! y si no queires tirarla tienes que ir haciendo esto:

   public static void statusCode(HttpExchange exchange, int statusCode) {
        try {
            exchange.sendResponseHeaders(statusCode, 0);
        } catch (IOException e) {
            try {
                exchange.sendResponseHeaders(500, 0);
            } catch (IOException e2) {
                try {
                    exchange.sendResponseHeaders(500, 0);
                } catch (IOException e3) {
                    throw new RuntimeException(e3);
                }
            }
        }
    }

Si no el usuario nunca recibira el 500 de vuelta, recibira seguramente un client disconnected de HTTP, no tu 500 o tu custom error que quieras mandarle en caso de excepcion.

Y de nuevo IOException es una excepcionar generica:

// Main IOException subclasses:

// File Operations
FileNotFoundException
FileSystemException
    FileAlreadyExistsException 
    NoSuchFileException
    DirectoryNotEmptyException

// Socket/Network Operations  
SocketException BindException ConnectException SocketTimeoutException UnknownHostException // Stream Operations EOFException ObjectStreamException StreamCorruptedException InvalidClassException
UTFDataFormatException // Pipe Operations CharacterCodingException InterruptedIOException

Todo esto lo cazara una IOException. Pero tu deberias comprobar TODO uno a uno, o que la libreria que uses devuelva el error exacto. De esta manera, pues puedes gestionar las cosas como se deben gestionar.

Con Java es IMPOSIBLE hacer las cosas bien. Es mala practica tras mala practica.

En mi caso como se ve, pues pongo el corte en capas externas donde si algo falla, el cliente desconectara / no recibira nada y no hace falta gestionar. Pero si tratase de hacer algo mas serio y critico no se podria en Java.

desu

Hoy vamos a hacer algo distinto, ya tenemos la base del servidor HTTP, ahora vamos a hacer un cliente HTTP básico.

Voy a usar un endpoint para consultar otro endpoint de manera recursiva a la proxy. Pero no sera un proxy al uso porque abriré una nueva conexión, pero de esta manera podré desarrollar las dos cosas en el mismo proyecto sin añadir demasiada complejidad.

Y creo que después de este paso, ya va a ser hora de meterle HTTPS y unos tests de integración.

1
desu

He añadido un fetch, que hace un getAll recursivo.

https://github.com/vrnvu/java-no-spring/commit/911520f4b793cf06e98724a7de42a7aaa65df7b4

Lo he hecho mal a proposito, asi ahora se puede arreglar. Pero vamos a comentar la porqueria que es Java.

  • Primero tenemos un cliente Http, este cliente se tiene que re-usar o vas a estar incializando conexiones y mierdas todo el rato.
  • Segundo la request la podemos re-usar? Bobada de pregunta, alguno que sepa como suelen funcionar las librerías HTTP pensara, pero esto en Rust es obvio debido al modelo de ownership! Asique punto positivo para Rust.
  • Tercero aqui lo peor las excepciones de nuevo.

Tenemos que tirar una excepcion en una lambda... esto es una puta locura, esto es lo peor de lo peor que se puede hacer. Mala practica encima de diez mil malas practicas por mil motivos. Pero es la menera IDIOMATICA, en Java tienes esta completionexception que debes usar...

throw new CompletionException(new TodoException("Failed to parse todos", e));

Creo que es el peor codigo que he visto en meses, y estamos hablando que esto lo ha diseñado la gente que diseña la libreria oficial de Java y mantiene el lenguaje y las VM.... Que locura... Ya les da igual todo. A meter features cueste lo que cueste, sea como sea.

Arreglos:

Cual es la mejor manera de tener la URI?

  • tenerla en una variable de entrono
  • tenerla en un archivo de configuracion
  • pasarla al constructor del servicio
  • pasarla como parametro a la funcion que la usa
desu

En el anterior post pregunte:

Cual es la mejor manera de tener la URI?
Cual es la mejor manera de tener la URI?
Cual es la mejor manera de tener la URI?

He puesto 4 opciones tipicas:

  • tenerla en una variable de entrono
  • tenerla en un archivo de configuracion
  • pasarla al constructor del servicio
  • pasarla como parametro a la funcion que la usa

Venga, la solución:

Por que esta es la solucion correcta?
Y la respuesta no es: DEPENDE, no depende de nada. Esta es la correcta.

desu

Disculpad lectores. Me olvide de un problemilla que comente sobre sqlite que aun no he arreglado. Antes de hacer el HTTPs. Creo que el cliente asi esta bien. Mi experiencia ha sido comoda, pero de nuevo ha habido el problema del async + lambda + excepciones que es receta para el desastre!

Ahora mismo si dos peticiones me piden un PUT /todos.

  • Id = 1, Title = Hello
  • Id = 1, Title = World

Hay una race condition con el statement que tengo que gestionar.


Fijaros en la linea que he añadido marcada de verde, estoy sobre-escribiendo el valor que me pasa el usuario. Hago esto a mano para demostrar que los statement no son seguros, se pueden escribir multiples veces, y si tenemos un modelo con corrutinas se puede dar que me lleguen dos peticiones de update como he puesto el ejemplo. Una sobre-escriba otra, y creo que lo que pasara es que el statement fallara! Vamos a arreglarlo y ya comprobaremos que todo ande bien cuando esten los tests de integracion.

Si yo hago:

curl -X 'PUT' \
    'http://localhost:8080/todos' \
    -H 'accept: text/plain' \
    -H 'Content-Type: application/json' \
    -d '{"id": 1, "title": "world", "completed": false}'

El servidor sobre-escribe con ese "2" que he hecho para demostrar la race condition:

Jan 16, 2025 12:20:47 PM com.github.vrnvu.todos.TodoSqlite insertTodo
INFO: Inserting todo Todo[id=1, title=world, completed=false]
desu

Los locks es un tema muy complejo, hay que saber:

  • Locks/Mutex
  • ReadWriteLock, Read write mutex.
  • Lock ownership, quien controla el lock? Se puede copiar el lock? Se puede clonar el lock? Que pasa si clonas la referencia? Es seguro?
  • Lock posioning, puede ser que un lock se rompa y no sea util nunca mas si lo anterior no lo haces bien.
  • Cuando es seguro soltar el lock?

En este caso, vamos a empezar con un Readwritelock, lo ponemos siempre en la scope mas pequeña posible y recordar todos los puntos anteriores. En Rust el modelo de borrow y ownership te ayuda con todo esto, en Java, si alguien viene y te copia el lock o crea 2 TodoSqlite.java ya la has jodido.

He hecho lo que he comentado, no estoy seguro si hay casos en que no funciona, estoy casi seguro que ahora mismo todo va bien. Pero es muy facil romper Java.


Mucha gente se cree que los mutex son caros de ejecutar. No es cierto.
Mucha gente cree que tener algo lock-free es mas rapido. No es cierto tampoco.

El mutex deberia ser tu primera opción siempre, empieza con un mutex que cubra mucha superficie y puedes trabajar en hacerlo mas pequeño con mini mutex. Por ejemplo tener un Mutex<HashMap<T>> vs tener un HashMap<Mutex<T>> o cosas del estilo. Yo he hecho much benchmarking y optimizaciones y diria que por lo general no vale la pena perder el tiempo en esto.

Lo mas importante cuando introduces un mutex es que siempre debe funcionar. correcto > .

Imagina otro caso, donde tienes que hacer un PUT y un GET a sqlite en la misma operación, primero harias un lockwrite() después un lockread(). Puedes soltar el lockwrite() cuando estas a medias? como garantizas la transaccionalidad si algo falla? Es un tema muy complejo y dificil a la que pongas un par de operaciones. Por eso siempre mejor delegar a la DB o al kernel para que lo haga transaccional por ti.

Si sueltas locks en medio de operaciones logicas, desde el punto de vista del cliente tu servicio puede estar roto, pero desde el punto de vista del servicio y la Db todo esta perfecto! es tu servicio quien tiene que mantener los invariantes adecuados.

desu

Los locks de java no implementan Autoclosable por que?

Esto nos permitira usarlos asi y que se liberen automáticamente?

    @Override
    public Optional<Todo> getTodoById(String id) throws SQLException {
        try (var ignored = readLock()) {        }
    }

No se puede por lo que he comentado arriba, Java no tiene garantias de que si alguien se ha copiado esa referencia, ahora tienes 2 tios lockeandote, o a saber que cojones puede pasar. Corrupción en memoria, undefined behaviours... etc etc

1 respuesta
JuAn4k4

#22 Has metido un log entre lock y try para release, un fpero fijo te metería un NPE en lo que se loggea en un caso edge y se lia parda.
Que benchmarks has hecho de CAS vs Locks ? Los tienes por ahí disponibles ?
A mi en general me parece más seguro usar CAS para no tener locks por ahí colgados porque el OS o lo que sea ha explotado. Con CAS no es problema aunque tienes otros claro.

2 respuestas
desu

#23

#23JuAn4k4:

Has metido un log entre lock y try para release, un fpero fijo te metería un NPE en lo que se loggea en un caso edge y se lia parda.

wut

#23JuAn4k4:

A mi en general me parece más seguro usar CAS para no tener locks por ahí colgados porque el OS o lo que sea ha explotado.

un lock esta poisoned si otro thread a tirado una excepcion o petado, en rust lo que pasa es que cuando tiras el lock() te va a devolver que esta poisoned

lo que hace el guard del mutex es comprobar el estado y settear a posioned

en java obvio no te enteras de nada

edit:

cas requieres que tu cache lines sean exclusiva, necesitas usar barriers o tendras bugs para que tu CPU pueda ordenar las operaciones, tener muchas cosas en un spinlock(q no mutex) como enseña pikus es mas rapido

edit2:

en este caso como es algo de IO uso mutex claro, no deberias hacer spinning con IO

edit3:

un tema interesante serian los interrupts y signals como que pasa si mato mi app mientras esta escribiendo, ahora mismo no lo gestiono, deberia tener minimo un gracefull shutdown que deberia ser suficiente para la mayoria de casos. si es algo critico deberiamos tener un file lock y delegar al kernel. y luego tratar de hacer recovery si hace falta. lo que sqlite ya lo tiene, puse un video el otro dia de como funciona sqlite por dentro.

min 33:30 mas o menos, journal y rollback WAL

no se si me he dejado nada relacionado con lo que has comentado. atomicos y locks resuelven problemas distintos, uno es memoria el otro secuencial. hablar de crasheos y panics es meterse en follones. mejor tener un buen recovery y no complicarse. asi funcionan las DB.

D

#8 Hasta las bolas de tener que meter 40 dependencias para 3 tonterias xD, la de problemas con versiones que me he encontrado al final intento usar lo menos posible y hacerlo lo mas sencillo que pueda, por que toca los huevos mucho.
Hace tiempo que tengo ganas de aprender go, pero a saber que me toca hacer en las prácticas...

smarquezp

Me mola que utilices MV como bloc de notas, dejas estas cosas interesantes jajajajaja

desu

Estoy tratando de usar las excepciones de java para gestionar errores pero es una puta porqueria inmensa.

En Go o Rust es todo super facil, eficiente, no tiene penalizaciónes en runtime y todo encaja facil. Puedes tener errores a nivel de libreria/componente.

En Java todo es un caos y las librerías tienen una usabilidad pobre.

Estoy considerando re-hacer todo con Either de Vertx o un Result simple a mano.

Lecherito

Hazlo en kotlin, es de jetbrains

desu

Una idea que se me ha ocurrido es meter las excepciones en una abstract sealed class que implementa Exception para que sea checked. El problema es que los switch pattern de Java son pobres (SON UNA PUTA PORQUERIA QUE NO VALEN PARA NADA) y necesito añadir un tag/enum type para luego hacer pattern matching. otro probelma es que tengo que hacer pattern matching exhaustivo de cosas que quizas no tienen sentido o tener un default que pete para poder corregirlo.

https://github.com/vrnvu/java-no-spring/commit/eaa51879a7e64736c5bb505927dfa5a1e22c32a1

Si hago un delete:

        try {
            todoService.deleteTodoById(id);
            Handler.statusCode(exchange, 200);
        } catch (TodoError e) {
            switch (e.getType()) {
                case NOT_FOUND -> Handler.statusCode(exchange, 404);
                case SYSTEM_ERROR -> {
                    logger.log(Level.SEVERE, e.getMessage(), e.getCause());
                    Handler.statusCode(exchange, 500);
                }
            }
        }

También podriamos tener el status code y asi nuestro Handler de HTTP que construye las respuestas podria hacer dispatch de la excepcion sin pensar mucho.

Para este caso de uso es mejor tener esta API:

            Handler.statusCode(exchange, 404);

Pero si que poder hacer esto con nuestro TodoError esta fino:

            
Handler.exception(exchange, e);

Podriamos mover el logger y que también sea automatico.

En conclusión: ES PERDER EL TIEMPO PENSAR EN ESTAS CHORRADAS, ABSTRACCIONES INUTILES, NO LO VOY A TOCAR

El problema es el pattern matching de java y las excepciones, moviendo el problema no vas a solucionar el problema.

Moviendo el problema quizas reduces la posibilidad de hacer un fallo, pero la posibilidad estara siempre ahi porque Java es un mal lenguaje de programación.

Una idea seria tener un HTTPErrorExceptionHandler como lo quieras llamar, que a partir de TODAS las excepciones que se lanzan lo mappeas a un error http y escribes en la response. Parecido a lo que Spring ofrece, sin el overtime de las anotaciones y con mejor control. pero de nuevo, si tiras mal una excepcion, el orden de los catch no esta bien, te va a fallar el codigo.

En el siguiente post enseño como seria esta idea, la calve va a ser trabajar a un nivel GRANULAR de excepciones muy fino, es decir, tener excepciones para ABSOLUTAMENTE todos los metodos. La alternativa seria lo contrario, tener una Excepcion custom y DENTRO tener tags y/o strings para saber quien lo ha lanzado. Que es lo que hace java por defecto en sus excepciones con herencia (lo peor de lo peor) en lugar de composición.

Y la solución pragmática, pues meterle un Optional a todo o un bool si es un error de negocio, o como he comentado el Result o Either seria lo mas fino, pero la experiencia es muy mala.

1 respuesta
desu

https://github.com/vrnvu/java-no-spring/commit/0439645e5ad9a6fcb81e7d3a5fbb58289f289e3e

moviendo el problema no vas a solucionar el problema.

esto es completamente estupido, por mucho que te esfuerces en hacer esto mas "claro" o "limpio" solo estas creando abstracciones y funciones auxiliares para esconder el problema sobre la manta

fijaros que la usabilidad es la misma, pero he añadido una clase, que duplica mis types/excepcioens TodoError, asi que ahora tengo que mantener mas codigo que antes. donde esta el beneficio? en que pueda mover un check de un lado a otro o mover el logger? mover el logger lo puedo hacer igual...

el mayor error del fpero medio de java es crear clases y bobadas con patrones de diseño para mover problemas en lugar de resolverlos. en este caso las excepciones de java no tienen solución, hemos llegado al limite, hora de abandolarlas por un approach funcional.