Próbuję zaprojektować aplikację, która ma złożoną domenę biznesową i wymaga obsługi interfejsu API REST (nie tylko REST, ale zorientowana na zasoby). Mam problem z wynalezieniem modelu domeny w sposób zorientowany na zasoby.
W DDD klienci modelu domeny muszą przejść przez warstwę procedur „Application Services”, aby uzyskać dostęp do dowolnej funkcji biznesowej, wdrożonej przez Entities i Domain Services. Na przykład istnieje usługa aplikacji z dwiema metodami aktualizacji encji użytkownika:
userService.ChangeName(name);
userService.ChangeEmail(email);
Interfejs API tej usługi aplikacji udostępnia polecenia (czasowniki, procedury), a nie stan.
Ale jeśli musimy również dostarczyć interfejs API RESTful dla tej samej aplikacji, istnieje model zasobów użytkownika, który wygląda następująco:
{
name:"name",
email:"[email protected]"
}
Zorientowany na zasoby interfejs API udostępnia stan , a nie polecenia . Rodzi to następujące obawy:
każda operacja aktualizacji interfejsu API REST może być odwzorowana na jedno lub więcej wywołań procedur usługi aplikacji, w zależności od tego, jakie właściwości są aktualizowane w modelu zasobów
każda operacja aktualizacji wygląda jak atomowa dla klienta REST API, ale nie jest tak zaimplementowana. Każde wywołanie usługi aplikacji jest zaprojektowane jako osobna transakcja. Aktualizacja jednego pola w modelu zasobów może zmienić reguły sprawdzania poprawności dla innych pól. Musimy więc zweryfikować wszystkie pola modelu zasobów razem, aby upewnić się, że wszystkie potencjalne wywołania usługi aplikacji są prawidłowe, zanim zaczniemy je tworzyć. Sprawdzanie poprawności zestawu poleceń jednocześnie jest o wiele mniej trywialne niż wykonywanie pojedynczych poleceń. Jak to robimy na kliencie, który nawet nie wie, że istnieją poszczególne polecenia?
wywoływanie metod usługi aplikacji w innej kolejności może mieć inny efekt, a interfejs API REST sprawia, że wygląda na to, że nie ma różnicy (w ramach jednego zasobu)
Mógłbym wymyślić więcej podobnych problemów, ale w zasadzie wszystkie one są spowodowane przez to samo. Po każdym wywołaniu usługi aplikacji zmienia się stan systemu. Zasady obowiązującej zmiany, zestaw działań, które jednostka może wykonać następną zmianę. Zorientowany na zasoby interfejs API stara się, aby wszystko wyglądało jak operacja atomowa. Ale złożoność przekroczenia tej luki musi gdzieś zniknąć i wydaje się ogromna.
Ponadto, jeśli interfejs użytkownika jest bardziej zorientowany na polecenia, co często ma miejsce, będziemy musieli odwzorować polecenia i zasoby po stronie klienta, a następnie ponownie po stronie interfejsu API.
Pytania:
- Czy cała ta złożoność powinna być obsługiwana przez (grubą) warstwę odwzorowania REST na AppService?
- A może brakuje mi czegoś w rozumieniu DDD / REST?
- Czy REST może po prostu być niepraktyczny do ujawnienia funkcjonalności modeli domen w pewnym (dość niskim) stopniu złożoności?
źródło
Odpowiedzi:
Miałem ten sam problem i „go rozwiązałem”, modelując zasoby REST inaczej, np .:
Więc zasadniczo podzieliłem większy, złożony zasób na kilka mniejszych. Każdy z nich zawiera nieco spójną grupę atrybutów oryginalnego zasobu, która ma być przetwarzana razem.
Każda operacja na tych zasobach ma charakter atomowy, nawet jeśli może być zaimplementowana przy użyciu kilku metod serwisowych - przynajmniej w Spring / Java EE nie jest problemem utworzenie większej transakcji z kilku metod, które pierwotnie miały mieć swoją własną transakcję (przy użyciu transakcji WYMAGANEJ) propagacja). Często nadal musisz przeprowadzić dodatkową weryfikację tego specjalnego zasobu, ale nadal jest to całkiem możliwe do zarządzania, ponieważ atrybuty są (powinny) być spójne.
Jest to również dobre dla podejścia HATEOAS, ponieważ twoje bardziej szczegółowe zasoby przekazują więcej informacji o tym, co możesz z nimi zrobić (zamiast mieć tę logikę zarówno na kliencie, jak i serwerze, ponieważ nie można jej łatwo przedstawić w zasobach).
Oczywiście nie jest doskonały - jeśli interfejsy użytkownika nie są modelowane z myślą o tych zasobach (zwłaszcza interfejsy zorientowane na dane), mogą powodować pewne problemy - np. Interfejs użytkownika prezentuje dużą formę wszystkich atrybutów danych zasobów (i jego pod-zasobów) i pozwala edytuj je wszystkie i zapisz od razu - tworzy to iluzję atomowości, nawet jeśli klient musi wywołać kilka operacji na zasobach (które same są atomowe, ale cała sekwencja nie jest atomowa).
Ponadto ten podział zasobów czasami nie jest łatwy ani oczywisty. Robię to głównie na zasobach o złożonych zachowaniach / cyklach życia, aby zarządzać ich złożonością.
źródło
Kluczową kwestią jest tutaj, w jaki sposób logika biznesowa jest wywoływana w sposób przejrzysty, gdy wykonywane jest wywołanie REST? Jest to problem, który nie jest bezpośrednio rozwiązany przez REST.
Rozwiązałem to, tworząc własną warstwę zarządzania danymi w oparciu o dostawcę trwałości, takiego jak JPA. Używając meta modelu z niestandardowymi adnotacjami, możemy wywoływać odpowiednią logikę biznesową, gdy zmienia się stan encji. Zapewnia to, że niezależnie od tego, jak zmienia się stan jednostki, wywoływana jest logika biznesowa. Utrzymuje architekturę SUCHĄ, a także logikę biznesową w jednym miejscu.
Korzystając z powyższego przykładu, możemy wywołać metodę logiki biznesowej o nazwie validateName, gdy pole nazwy zostanie zmienione za pomocą REST:
Mając do dyspozycji takie narzędzie, wszystko, co musisz zrobić, to odpowiednio opisać metody logiki biznesowej.
źródło
Nie powinieneś ujawniać modelu domeny w sposób zorientowany na zasoby. Powinieneś udostępniać aplikację w sposób zorientowany na zasoby.
Wcale nie - wysyłaj polecenia do zasobów aplikacji, które łączą się z modelem domeny.
Tak, chociaż istnieje nieco inny sposób przeliterowania tego, który może uprościć sprawę; każda operacja aktualizacji w stosunku do interfejsu API REST odwzorowuje proces, który rozsyła polecenia do jednego lub większej liczby agregatów.
Gonisz tutaj niewłaściwy ogon.
Wyobraź sobie: całkowicie usuń REST ze zdjęcia. Wyobraź sobie, że piszesz interfejs pulpitu dla tej aplikacji. Wyobraźmy sobie, że masz naprawdę dobre wymagania projektowe i wdrażasz interfejs użytkownika oparty na zadaniach. Tak więc użytkownik otrzymuje minimalistyczny interfejs, który jest idealnie dostrojony do wykonywanego zadania; użytkownik określa niektóre dane wejściowe, a następnie „VERB!” przycisk.
Co się teraz stanie? Z punktu widzenia użytkownika jest to jedno zadanie atomowe do wykonania. Z perspektywy domainModel jest to szereg poleceń uruchamianych przez agregaty, przy czym każde polecenie jest uruchamiane w osobnej transakcji. Te są całkowicie niezgodne! Potrzebujemy czegoś pośrodku, aby wypełnić lukę!
Coś jest „aplikacją”.
Na szczęśliwej ścieżce aplikacja odbiera trochę DTO i analizuje ten obiekt, aby uzyskać zrozumiały komunikat, i wykorzystuje dane w komunikacie do tworzenia dobrze sformułowanych poleceń dla jednego lub większej liczby agregatów. Aplikacja upewni się, że każde polecenie, które wysyła do agregatów, jest dobrze uformowane (jest to działająca warstwa antykorupcyjna) i załaduje agregaty i zapisze agregaty, jeśli transakcja zakończy się pomyślnie. Agregat sam zdecyduje, czy polecenie jest prawidłowe, biorąc pod uwagę jego bieżący stan.
Możliwe wyniki - wszystkie komendy działają poprawnie - warstwa antykorupcyjna odrzuca komunikat - niektóre komendy działają poprawnie, ale potem jedna z agregacji narzeka i masz możliwość zaradzenia.
Teraz wyobraź sobie, że masz tę aplikację zbudowaną; jak współdziałasz z nim w sposób RESTful?
Zaakceptowane jest zwykłe wylogowywanie, gdy aplikacja zamierza odroczyć przetwarzanie komunikatu do momentu odpowiedzi na klienta - zwykle używane przy akceptowaniu polecenia asynchronicznego. Ale działa również dobrze w tym przypadku, w którym operacja, która ma być atomowa, wymaga ograniczenia.
W tym idiomie zasób reprezentuje samo zadanie - zaczynasz nową instancję zadania, publikując odpowiednią reprezentację w zasobie zadania, a ten zasób łączy się z aplikacją i kieruje cię do następnego stanu aplikacji.
W ddd , prawie za każdym razem, gdy koordynujesz wiele poleceń, chcesz myśleć w kategoriach procesu (inaczej proces biznesowy, inaczej saga).
W modelu do odczytu występuje podobne niedopasowanie pojęciowe. Ponownie rozważ interfejs oparty na zadaniach; jeśli zadanie wymaga modyfikacji wielu agregatów, wówczas interfejs użytkownika do przygotowania zadania prawdopodobnie zawiera dane z wielu agregatów. Jeśli twój plan zasobów to 1: 1 z agregatami, będzie to trudne do zorganizowania; zamiast tego należy udostępnić zasób, który zwraca reprezentację danych z kilku agregatów, wraz z formantem hipermedia, który odwzorowuje relację „uruchomienie zadania” na punkcie końcowym zadania, jak omówiono powyżej.
Zobacz także: REST w praktyce autorstwa Jima Webbera.
źródło