#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.