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ść:
- 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
)
) ) )
-
Bardzo podobna biblioteka Octopus: https://github.com/krzemin/octopus
-
Najświeższa biblioteka Combos: https://github.com/rewards-network/combos
-
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")
}
-
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/) -
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