Wpis z mikrobloga

Mam takie zagadnienie:

- istnieje encja Osoba ( imie, drugieImie, plec, dataChrztu )
- istnieje punkt końcowy "update" pozwalający zaktualizować encję
- przekazujemy tylko te własności, jakie chcemy zmienić
- na przykład {"imie":"Jacek","drugie_imie":null}
- osoby zapisujemy do bazy Mongo

Czyli istnieje sobie klasa:

java
@Data
class Osoba {
private String imie;
private String drugieImie;
private Plec plec;
private LocalDate dataChrztu;
}

Istnieje już jedna encja w Mongo:

json
{"imie":"Adam","drugie_imie":"Alan","plec":"M","dataChrztu":"2000-01-01"}

Teraz przychodzi update - chcemy zaktualizować tylko te 2 konkretne pola:

json
{"imie":"Jacek","drugie_imie":null}

Czyli zmiana imienia na Jacek i usunięcie drugiego imienia.

Wszystko fajnie, jeśli operujemy na mapach / tablicach asocjacyjnych / czy co tam w jakim języku istnieje.

Ale jak na wejściu od razu mapujemy do encji Osoba?

java
@patch
@path("/{id}")
public Osoba update(@pathParam Long id, Osoba patch) {
serwis.zaktualizuj(id, patch);
}

Problem w tym, że:

- pozostałe pola klasy Osoba zostaną zainicjowane jako null
- jeśli przy update zignorujemy pola null, to nie wyzeruje się drugie imię
- jeśli przy update nie zignorujemy pól null, to błędnie wyzeruje się płeć i data chrztu

To może inaczej?

java
@patch
@path("/{id}")
public Osoba update(@pathParam Long id, Map<String,Object> patch) {
serwis.zaktualizuj(id, patch);
}

Ok, teraz w mapie mamy tylko te pola, co chcemy zaktualizować i to powinno zadziałać.

https://stackoverflow.com/questions/10290621/how-do-i-partially-update-an-object-in-mongodb-so-the-new-object-will-overlay

Czyli robimy $set

Używam Panache w Quarkusie i tam jest PanacheMongoRepositoryBase::update() który to robi.

Ale jest jeszcze jeden problem, bo trzeba zrobić walidację, czy takie pola w encji faktycznie istnieją i czy typ się zgadza, bo nie chcemy, aby ktoś wrzucił śmieci i potem aplikacja się wysypie. Jeśli pól w encji jest 100, to pisanie takich walidacji ręcznie jest czasochłonne. Jest kilka opcji, aby taką walidację przeprowadzić automatycznie:

- dla każdego klucza w mapie - sprawdzić, czy w klasie Osoba istnieje pole o takiej nazwie, a jeśli tak, to czy typy się zgadzają - no i z typami może być problem, bo o ile porównanie typów liczbowych i ciągów znaków spoko, o tyle daty (jest wiele klas i wiele formatów dat), enumy i nie wiadomo co jeszcze - brzmi jak wynajdowanie koła na nowo

- spróbować utworzyć instancję klasy i wrzucić wartości - co przy m.in. datach też może być czasochłonne

- ObjectMapper z biblioteki Jackson - jeśli skonwertuje mapę do klasy Osoba, to uznajemy, że jest ok - przy czym trzeba go skonfigurować tak, by rzucał wyjątkami, gdy pola nie ma w klasie lub gdy się typy nie zgadzają

A może w ogóle źle podchodzę do tematu? Może coś takiego już istnieje w Panache lub frameworku Quarkus?

#quarkus #java #mongodb #programowanie #panache
  • 9
@SendMeAnAngel: a dlaczego nie hashmapa? Używanie obiektów entity do reprezentacji częściowych zmian nie jest koszerne. Zmiana danych Osoby to inny byt niż sama Osoba, więc one nie powinny być w kodzie reprezentowane ta sama klasą / struktura. Twój problem polega na tym że próbujesz wcisnąć dane niepasujące do klasy Osoba i tak powstaje niejednoznaczność bo klasa Osoba nie potrafi reprezentować wszystkich potrzebnych przypadków. Dlatego hashmapa lub inny obiekt dynamiczny (może być
@Krolik: @Myzreal: Ostatecznie zrobiłem tak, że na wejściu przychodzi Map<String,?>, a potem sprawdzam, czy dane pola istnieją w klasie Osoba i konwertuję wartości mapy za pomocą ObjectMapper (żeby nie wynajdować koła na nowo).