Wpis z mikrobloga

Krótki suplement do poprzedniego wpisu. Jak mapuje błędy na odpowiedzi http. Trochę mniej treści, a więcej kodu.

Mam jeden interface na wszystkie błędy:

public interface ResponseError {
String getMessage();
int getHttpCode();
}

Przykładowe enum:

public enum UserError implements ResponseError {
//normalnie jest ich oczywiście więcej
EMPTY_USERNAME_OR_PASSWORD("Empty username or password", 400),
DUPLICATED_USERNAME("Duplicated username", 400);

private int httpCode;
private String message;

UserError(String message, int httpCode) {
this.httpCode = httpCode;
this.message = message;
}

@Override
public String getMessage() {
return message;
}

@Override
public int getHttpCode() {
return httpCode;
}
}

Narzuciłem sobie, aby wszystkie fasady zwracały:

Either extends ResponseError, ?
Dzięki temu mam jeden generyczny mapper:

public final class ResponseResolver {
private ResponseResolver() {}

static ResponseEntity resolve(Either extends ResponseError, ? input) {
return input
.map(ResponseEntity::ok)
.getOrElseGet(ResponseResolver::createErrorResponse);
}

private static ResponseEntity createErrorResponse(ResponseError error) {
ErrorResponse response = new ErrorResponse(error.getMessage());
int httpCode = error.getHttpCode();
return new ResponseEntity<>(response, HttpStatus.valueOf(httpCode));
}
//Bonusowo na tej samej zasadzie działa z Option (Optionalem)
public static ResponseEntity resolve(Option input) {
return input
.map(x -> new ResponseEntity<>(x, HttpStatus.OK))
.getOrElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}

(Gdzie klasa ErrorResponse jest zwykłym DTO z jednym polem, żeby wynik był jsonem)

No i teraz w kontrolerze (na przykładzie springa):

@PostMapping("login")
public ResponseEntity generateToken(@RequestBody LoginUserInfo loginUserInfo) {
Either userDetails = userFacade.login(loginUserInfo);
return ResponseResolver.resolve(userDetails);
}

No i właściwie to tyle ;)

Powyższe rozwiązanie ma właściwie pewną wadę: miesza warstwy. Czy jest to problem? Zazwyczaj nie, bo i tak pisze się soft dedykowany pod pracę na serwerach i nikt go w inny sposób nie odpala. Dodatkowo ta nadmiarowa informacja w postaci kodu http i wiadomości nie przeszkadza w żaden sposób w odpaleniu kodu np. z konsoli. Rozwiązaniem tego mógłby być osobny mapper w warstwie http, ale to już wchodzi trochę w sztukę dla sztuki, więc wydaje mi się, że taki kompromis pomiędzy pragmatycznością, a łamaniem abstrakcji między warstwami jest jeszcze akceptowalny.

#java #webdev #programowanie
  • 22
  • Odpowiedz
  • Otrzymuj powiadomienia
    o nowych komentarzach

@krasnoludkolo: Hmm, w przypadku gdy sam bym pisał klienta aplikacji, to w idealnej sytuacji żaden wyjątek nigdy mi nie poleci (jedynie jak ktoś zacznie próbować sam uderzać pod API)

To mam jeszcze inne pytanie, jak obsługujesz wyjątki które nie lecą w twoim kodzie tylko w jakieś klasie z liba? robisz try { } catch { } przy każdym wywołaniu zewnętrznej klasy która może rzucić wyjątek?
  • Odpowiedz
@badAttitude: no generalnie sterowanie wyjątkami to jest takie krypto goto. Utrudnia to czytanie, funkcje nie są "czyste". Tak to może i napiszesz kilka znaków więcej, ale już po samej sygnaturze wiesz czego się spodziewać.
  • Odpowiedz
@badAttitude: jedno wynika z drugiego. Taki np brak użytkownika nie jest według mnie "wyjątkową" sytuacją tylko normalną do obsłużenia przez kod. Dodatkowo lokiga domenowa nie powinna nic wiedzieć o tym czy to leci potem restem czy do ui czy cokolwiek
  • Odpowiedz