Scala - Walidacja DTO i obsługa błędów w Akka HTTP

2

Cześć!

Ostatnio wróciłem do swojego małego projektu w Scali oraz Akka HTTP, który pokazywałem 2 miesiące temu. Najważniejsze operacje CRUD są gotowe, tylko zastanawiam się nad rozwiązaniem walidacji DTOs reprezentowanych jako case class i obsługi błędów w Akka HTTP.

Przyznam, że w Spring Boot dzięki JSR-303 i powiedzmy adnotacji @ControllerAdvice (tak wiem, side effects i wyrzucanie wyjątków w serwisach) walidacja była jednak łatwiejsza i szybsza do zrobienia :D

Ogólnie w REST API chciałbym zwracać błędy klientowi podczas walidacji pól w DTOs w JSONie np w takim raczej standardowym formacie:

{
  "status": 400,
  "errors": [
    {
      "fieldName": "url",
      "message": "Name cannot be empty."
    },
    {
      "fieldName": "title",
      "message": "Title cannot be empty."
    },
    {
      "fieldName": "description",
      "message": "Description cannot be empty."
    }
  ]
}

Zrobiłem rozenanie jak może wyglądać obsługa błędów i jest kilka różnych podejść:

  1. Użycie bilbioteki do walidacji np. Accord
    Jest to biblioteka która pozwala w łatwy sposób komponować błędy i pozwoli na zwracanie wielu błędów. Przykład ze strony:

import com.wix.accord.dsl._    // Import the validator DSL

case class Person( firstName: String, lastName: String )
case class Classroom( teacher: Person, students: Seq[ Person ] )

implicit val personValidator = validator[ Person ] { p =>
  p.firstName is notEmpty                   // The expression being validated is resolved automatically, see below
  p.lastName as "last name" is notEmpty     // You can also explicitly describe the expression being validated
}

implicit val classValidator = validator[ Classroom ] { c =>
  c.teacher is valid        // Implicitly relies on personValidator!
  c.students.each is valid
  c.students have size > 0
}

// Executing the validation

// Import the library
import com.wix.accord._

// Validate an object successfully
val validPerson = Person( "Wernher", "von Braun" )
val result: com.wix.accord.Result = validate( validPerson )   // Validator is implicitly resolved
assert( result == Success )

// Or get a detailed failure back:
val invalidPerson = Person( "", "No First Name" )
val failure: com.wix.accord.Result = validate( invalidPerson )
assert( failure == Failure( Set(                          // One or more violations
  RuleViolation(                                          // A violation includes:
    value = "",                                           //   - The invalid value
    constraint = "must not be empty",                     //   - The constraint being violated
    path = Path( Generic( "firstName" ) )                 //   - A path to the violating property
  )
) ) )
  1. Bardzo podobna biblioteka Octopus: https://github.com/krzemin/octopus

  2. Najświeższa biblioteka Combos: https://github.com/rewards-network/combos

  3. Użycie dyrektywy validate() albo metody require() ze standardowej biblioteki Scali np tak jak tu:

case class Color(name: String, red: Int, green: Int, blue: Int) {
  require(!name.isEmpty, "color name must not be empty")
  require(0 <= red && red <= 255, "red color component must be between 0 and 255")
  require(0 <= green && green <= 255, "green color component must be between 0 and 255")
  require(0 <= blue && blue <= 255, "blue color component must be between 0 and 255")
}
  1. Użycie walidacji dostępnej w bibliotece Cats np poprzez Validated lub:
    Blog SoftwareMill
    GitHub
    https://www.gregbeech.com/2018/08/12/akka-http-entity-validation/)

  2. Użycie jakieś biblioteki do walidacji z Javy np. JSR-303? (Tak wiem, że to zły pomysł, ale Akka HTTP nie ma aż tak dobrej walidacji jednak :D)

Wydaje mi się, że użycie biblioteki Accord będzie najlepsze. Zastanawiam się tylko czy warto, bo chciałbym ją użyć również do innego komercyjnego projektu, a właściwie mikroserwisów którę będę pisał w Scali i będzie tam oczywiście walidacja i obsługa błędów z DTOsów jako case class.
Ma niby 9 kontrybutorów i 520 gwiazdek na GitHub, ale ostatnie commity są 2 lata temu

Swój nowy projekt chciałbym pisać w Scali 3, i boję się trochę braku kompatybilności wstecz tej biblioteki, chociaż ma wersję dla Scali 2.13 i ostatni build z marca 2020.

Które podejście do walidacji DTOs/ obsługi błędów wybralibyście i które podejście stosujecie u siebie? :D

Wołam standardowo @KamilAdam, @Wibowit, @jarekr000000

2

Z bibliotek do walidacji używałem tylko Octopus, nawet spoko, ale z początku dziwne. Ma też integrację z Catsami i Scalaz, ale używałem wtedy jeszcze gołej wersji

Jak boisz się braku wsparcia zewnętrznych bibliotek to użyj Catsów , albo własnego ValidatedNel :P BWT jeśli z jakiegoś powodu chcesz zwracać zarówno informacje o walidacji jak i walidowany obiekt to zamiast ValidatedNel trzeba użyć Writer

Na własnoręcznie napisanym Writerze (już po merdzu do mastera zorientowałem się że to Writer :D ) napisałem wielką biznesową walidację i IMHO jest czytelna :P

2

Przeważnie używałem gołych Validation i \/ ze scalaz. Ale nie miałem nigdgy tak tłustych DTO, żeby to był problem.

Znając obecne trendy to pewnie niedługo będzie ZIOValidation :-), ale nie znalazłem nic specjalnie ciekawego/ gotowego link

0

Dzięki wielkie za pomoc!

Ten projekt chcę skończyć jak najszybciej i wrzucić Wam do oceny, więc użyję tutaj którejś z bibliotek Accord/Octopus.
Za to w nowym projekcie w Scali 3, użyję Catsów lub Scalaz, żeby podszkolić te biblioteki i właśnie uniknąć problemów z kompatybilnością tych bibliotek do walidacji :D

Jeszcze takie 2 pytania:

  1. Próbowaliście używać Scali 3 do nowych projektów? Czy użylibyście w ogóle Scali 3 do robienia mikroserwisów w komercyjnym projekcie jeśli sami pisalibyście? :D
  2. Jak wygląda w Scali 3 sytuacja z Akka HTTP, Spray JSON czy wsparcie Intellij? Da się już normalnie korzystać i pisać soft?

Ciekawi mnie dalszy rozwój Javy, bo faktycznie dostaje coraz więcej funkcjonalności znanych ze Scali/FP jak rekordy (chociaż nadal nie mają metody copy() i podejścia update as you copy, don't mutate, więc bez porównania), czy próby wprowadzenia pattern matching do switcha.

1 użytkowników online, w tym zalogowanych: 0, gości: 1