Czy interfejsy API RESTful mają tendencję do zachęcania do anemicznych modeli domen?

34

Pracuję nad projektem, w którym staramy się zastosować zarówno projektowanie oparte na domenie, jak i REST do architektury zorientowanej na usługi. Nie martwimy się o 100% zgodność z REST; prawdopodobnie lepiej byłoby powiedzieć, że próbujemy budować API HTTP zorientowane na zasoby (~ Poziom 2 modelu dojrzałości REST Richardsona). Niemniej jednak staramy się trzymać z daleka od używania żądań HTTP w stylu RPC, tzn. Próbujemy implementować nasze czasowniki HTTP zgodnie z RFC2616 zamiast na przykład POSTrobić to IsPostalAddressValid(...), co robimy .

Wydaje się jednak, że nacisk na to leży kosztem naszej próby zastosowania projektowania opartego na domenie. Z tylko GET, POST, PUT, DELETEi kilka innych rzadko stosowane metody, mamy tendencję do budowania usług cruddy i usługi cruddy mają tendencję do anemiczny model domeny.

POST: Odbierz dane, zweryfikuj je, zrzuć do danych. GET: Pobierz dane, zwróć je. Nie ma tam prawdziwej logiki biznesowej. Używamy również komunikatów (zdarzeń) między usługami i wydaje mi się, że większość logiki biznesowej kończy się wokół tego.

Czy REST i DDD są na pewnym poziomie napięcia? (A może coś tu źle rozumiem? Czy robimy coś innego źle?) Czy można zbudować silny model domeny w architekturze zorientowanej na usługi, unikając wywołań HTTP w stylu RPC?

Kazark
źródło
1
POST został celowo zaprojektowany, aby był „celowo niejasny”; wynik testu POST jest specyficzny dla implementacji. Co powstrzymuje cię przed robieniem tego, co robią Twitter i inni projektanci API, i definiowaniem każdej metody POST w części interfejsu API innej niż CRUD zgodnie z własnymi konkretnymi wymaganiami?
Robert Harvey
@RobertHarvey Konstruowaliśmy POST jako kreację. Ponowne spojrzenie na standard , być może jest to zbyt uproszczone. Na przykład, czy uważasz, że wykonanie testu POST IsPostalAddressValid(...)byłoby zgodne z „Zapewnieniem bloku danych, takiego jak wynik przesłania formularza, do procesu przetwarzania danych”?
Kazark
Jest tak, ponieważ nie ma czasownika UTWÓRZ (i nie ma czasownika UAKTUALNIJ). Twierdzę, że czasowników tych brakuje ze standardu (z jakiegokolwiek powodu), dlatego musisz wybrać opcję POST dla „wszystkiego innego”. Twój „proces przetwarzania danych” w tym przypadku jest procesem, który sprawdza adres pocztowy i zwraca wartość odpowiadającą wynikowi tej analizy.
Robert Harvey
1
@RobertHarvey: Wierzę, że POST i PUT / PATCH to po prostu czasownik CREATE i UPDATE, którego chciałeś. Jest inaczej nazwany, więc czasownik nadal ma jakiś sens, nawet w przypadku projektu bez REST.
Lie Ryan
@LieRyan: Dam ci to. Po prostu uważam, że CRUD z definicji oznacza anemiczne modele danych. Możesz zachować pewne zachowanie, jeśli, powiedzmy, jesteś w M MVC, ale na pewno nie w heterogenicznych systemach. Do wszystkiego innego oprócz CRUD potrzebujesz POST.
Robert Harvey

Odpowiedzi:

38

Pierwsza zasada systemów rozproszonych Martina Fowlera: „Nie rozpowszechniaj swoich obiektów!” Interfejsy zdalne powinny być gruboziarniste, a interfejsy wewnętrzne drobnoziarniste. Często bogaty model domeny ma zastosowanie tylko w ograniczonym kontekście .

Interfejs API REST oddziela dwa różne konteksty, z których oba mają własne modele wewnętrzne. Konteksty komunikują się przez interfejs gruboziarnisty (REST API) przy użyciu obiektów „anemicznych” (DTO).

W twoim przypadku brzmi to tak, jakbyś próbował rozszerzyć kontekst poza granicę, którą jest interfejs API REST. Może to prowadzić do drobnoziarnistego interfejsu zdalnego lub modelu anemicznego. W zależności od projektu może to stanowić problem.

simoraman
źródło
1
Fowler ma wiele dobrych przemyśleń, ale nie zapominajmy o katastrofie oryginalnej specyfikacji i implementacji EJB. Dopiero potem odkryli, że wywołania metod niskiego poziomu dla każdej drobnej operacji, takiej jak getName (), były koszmarem sieci / obciążenia. Interfejsy gruboziarniste stały się właściwą drogą, a wraz z nim koncepcja, że ​​całe wykresy / komunikaty encji zostały wysłane i odebrane w kontekście czasownika + rzeczownika.
Darrell Teague,
9

POST został celowo zaprojektowany, aby był „celowo niejasny”; wynik testu POST jest specyficzny dla implementacji. Co powstrzymuje cię przed robieniem tego, co robią Twitter i inni projektanci API, i definiowaniem każdej metody POST w części interfejsu API innej niż CRUD zgodnie z własnymi konkretnymi wymaganiami? POST jest czasownikiem catchall. Użyj go, gdy żaden z innych czasowników nie pasuje do operacji, którą chcesz wykonać.

Innymi słowy, twoje pytanie może być równie postawione, ponieważ „Czy„ inteligentne ”obiekty zachęcają do projektowania w stylu RPC?” Nawet Martin Fowler (który ukuł termin „Anemic Domain Model”) przyznaje, że nagie DTO mają pewne zalety:

Umieszczenie zachowania w obiektach domeny nie powinno być sprzeczne z solidnym podejściem polegającym na stosowaniu warstw do oddzielenia logiki domeny od takich rzeczy, jak trwałość i odpowiedzialność za prezentację. Logika, która powinna znajdować się w obiekcie domeny, to logika domeny - sprawdzanie poprawności, obliczenia, reguły biznesowe - jakkolwiek chcesz to nazwać.

Jeśli chodzi o model dojrzałości Richardsona , możesz dostać się na poziom 3, nie martwiąc się o „Anemiczne modele domen”. Pamiętaj, że nigdy nie zamierzasz przenosić zachowania do przeglądarki (chyba że planujesz wstrzyknąć JavaScript w swoich modelach).

REST dotyczy głównie niezależności maszyn; zaimplementuj model REST w takim stopniu, w jakim chcesz, aby punkty końcowe reprezentowały zasoby, oraz aby użytkownicy interfejsu API mogli łatwo uzyskać dostęp do tych zasobów i utrzymywać je w standardowy sposób. Jeśli to wydaje się anemiczne, niech tak będzie.

Zobacz także
Potrzebuję więcej czasowników

Robert Harvey
źródło
Myślę, że to z pewnością dotyczy strony pytania RFC2616. Co z faktem, że staramy się być zorientowani na zasoby, tj. Przynajmniej staramy się osiągnąć poziom 2 w modelu dojrzałości Richardsona dla REST?
Kazark
1
Czytam przez martinfowler.com/articles/richardsonMaturityModel.html . Możesz dostać się na poziom 3, nie martwiąc się o „Anemiczne modele domen”. Pamiętaj, że nigdy nie zamierzasz przenosić zachowania do przeglądarki (chyba że planujesz wstrzyknąć JavaScript w swoich modelach).
Robert Harvey
4

Interfejs API REST jest tylko jednym rodzajem warstwy prezentacji. Nie ma to nic wspólnego z modelem domeny.

Pytanie, które zadałeś, pochodzi z zamieszania, że ​​musisz jakoś dostosować się do siebie. Ty nie.

Mapujesz model domeny na interfejs API REST w taki sam sposób, jak mapujesz model domeny na RDBMS za pośrednictwem ORM - musi istnieć ta warstwa mapowania.

Domena ← ORM →
Domena RDBMS ← Mapowanie REST → API REST

Elnur Abdurrakhimov
źródło
3

IMHO Nie sądzę, aby zachęcały do ​​modelowania domen anemicznych (ADM), ale wymagają poświęcenia czasu i przemyślenia.

Po pierwsze, myślę, że główną cechą ADM jest to, że nie mają w nich żadnego lub wcale się nie zachowują. Nie oznacza to, że system nie zachowuje się, tylko że zwykle znajduje się w jakiejś klasie usług (patrz http://vimeo.com/43598193 ).

I oczywiście, jeśli takie zachowanie nie istnieje w ADM, to co? Odpowiedzią są oczywiście dane. I w jaki sposób mapuje się na API REST? Prawdopodobnie dane odwzorowują zawartość zasobu, a zachowanie odwzorowuje czasowniki HTTP.

Masz więc wszystko, czego potrzebujesz, aby zbudować bogaty model domeny, po prostu musisz zobaczyć, jak czasowniki HTTP odwzorowują operacje domeny na danych, a następnie umieścić te operacje w tych samych klasach, które zawierają dane.

Myślę, że ludzie mają problemy z tym, że mają trudności z zobaczeniem, jak czasowniki HTTP odwzorowują ich zachowanie w domenie, gdy zachowanie wykracza poza zwykłą CRUD, tj. Gdy występują skutki uboczne w innych częściach domeny poza zasób modyfikowany przez żądanie HTTP. Jednym ze sposobów rozwiązania tego problemu są zdarzenia domeny ( http://www.udidahan.com/2009/06/14/domain-events-salvation/ ).

RibaldEddie
źródło
3

Ten artykuł jest dość związany z tematem i wierzę, że odpowiada na twoje pytanie.

Podstawowa koncepcja, która moim zdaniem odpowiada bardzo dobrze na twoje pytanie, została streszczona w następującym akapicie wspomnianego artykułu:

„Bardzo ważne jest rozróżnienie między zasobami w interfejsie API REST i jednostkami domeny w projekcie opartym na domenie. Projekt oparty na domenie ma zastosowanie po stronie implementacji (w tym implementacji API), podczas gdy zasoby w interfejsie API REST sterują projektem interfejsu API i kontraktem. Zasób API wybór nie powinien zależeć od podstawowych szczegółów implementacji domeny. ”

Majix
źródło
1

Kilka rozsądnie udanych wdrożeń, które widziałem / zbudowałem, odpowiada na pytanie, w jaki sposób łączą one czasownik + metaforę rzeczownika przy użyciu gruboziarnistych metod „przyjaznych dla biznesu”, które działają na podmioty.

Więc zamiast (skazanej na atak) getName()metody / usługi ujawnij getPerson(), przekazując rzeczy takie jak typ identyfikatora / identyfikator, zwracając cały Personbyt.

Ponieważ zachowania encji Osoba w takim kontekście nie mogą być odpowiednio przekazane (ani też nie powinny być w kontekście skoncentrowanym na danych, jak to), całkowicie uzasadnione jest zdefiniowanie modelu danych (w stosunku do obiektu) dla par zapytania / odpowiedzi Usługi.

Same usługi i zdefiniowane czasowniki dodają pewne zachowania, kontrole, a nawet reguły przejścia do stanu dla jednostek. Na przykład istniałaby logika specyficzna dla domeny co do tego, co dzieje się w transferPerson()wywołaniu usługi, ale sam interfejs definiowałby tylko dane wejściowe / wyjściowe / dane bez definiowania ICH zachowań wewnętrznych.

Nie zgadzam się z autorami, którzy powiedzieliby na przykład, że implementacja czasownika przeniesienia należy do klasy Person lub jest powiązana z usługą Person-centric. Rzeczywiście, metoda transferu dotyczący Personoraz opcje ich (w tym prostym przykładzie) byłoby lepiej zdefiniowane przez Carrier, w którym Personmoże mieć żadnej wiedzy nawet, jakie są dostępne metody transferu lub jak przenoszenie nawet ma miejsce (kto wie Pracy Jak silników odrzutowych tak czy inaczej).

Czy to powoduje, że Personistota jest anemiczna? Nie wydaje mi się

Może / powinna istnieć logika w odniesieniu do rzeczy specyficznych dla Osoby, które są wewnętrzne dla Osoby, takich jak ich stan zdrowia, które nie powinny być definiowane przez klasę zewnętrzną.

Jednak w zależności od przypadków użycia jest całkowicie do przyjęcia, że ​​klasa jednostek nie ma ważnych / istotnych zachowań w niektórych systemach, takich jak usługa przydzielania miejsc w systemie transportowym. Taki system może równie dobrze implementować usługi oparte na REST, które zajmują się instancjami Person i powiązanymi identyfikatorami, ale nigdy nie definiują / implementują ich wewnętrznych zachowań.

Darrell Teague
źródło
Dobre punkty --- to naprawdę przynosi nową perspektywę, której inne odpowiedzi jeszcze nie miały.
Kazark
0

Czy masz problem z tym, że próbujesz wcisnąć swój model w podstawowy zestaw czasowników, używając POST tak często, jak to możliwe?

To nie jest konieczne - wiem, że dla większości ludzi REST oznacza POST, GET, PUT i DELETE, ale http rfc mówi:

Zbiór typowych metod dla HTTP / 1.1 jest zdefiniowany poniżej. Chociaż ten zestaw można rozszerzyć, nie można założyć, że dodatkowe metody współużytkują tę samą semantykę dla oddzielnie rozszerzonych klientów i serwerów.

Systemy takie jak SMTP używają tego samego stylu metod opartych na czasownikach, ale z zupełnie innym zestawem.

Więc nie ma powodu, dla którego musisz ich używać, możesz użyć dowolnego zestawu czasowników, który ci się podoba (chociaż naprawdę przekonasz się, że możesz zrobić wszystko, czego potrzebujesz w podstawowej 4 z odrobiną przemyślenia). Tym, co odróżnia REST od innych mechanizmów, jest jego bezstanowy i spójny sposób implementacji tych czasowników. Nie powinieneś próbować wdrażać systemu przekazywania wiadomości między warstwami, ponieważ zasadniczo nie wykonujesz REST, zamiast tego robisz mechanizm przekazywania wiadomości, RPC lub kolejki komunikatów, co niewątpliwie straci ci korzyści z REST (tj. jego prostota sprawia, że ​​działa naprawdę dobrze przez połączenie HTTP).

Jeśli potrzebujesz w pełni funkcjonalnego, złożonego protokołu przesyłania wiadomości, zbuduj go (jeśli możesz to zrobić przez Internet, istnieje powód, dla którego REST jest tak popularny), ale w przeciwnym razie staraj się trzymać projektu architektonicznego REST.

gbjbaanb
źródło