Projektowanie oparte na domenie - zewnętrzne zależności w problemie Entity

23

Chciałbym rozpocząć projektowanie oparte na domenie, ale jest kilka problemów, które chciałbym rozwiązać przed rozpoczęciem :)

Wyobraźmy sobie, że mam grupy i użytkowników, a gdy użytkownik chce dołączyć do grupy, wywołuję groupsService.AddUserToGroup(group, user)metodę. W DDD powinienem to zrobić group.JoinUser(user), co wygląda całkiem nieźle.

Problem pojawia się, jeśli istnieją pewne reguły sprawdzania poprawności dotyczące dodawania użytkownika lub niektóre zadania zewnętrzne muszą zostać uruchomione, gdy użytkownik zostanie dodany do grupy. Wykonywanie tych zadań spowoduje, że jednostka będzie miała zewnętrzne zależności.

Przykładem może być ograniczenie, że użytkownik może uczestniczyć tylko w 3 grupach maksymalnie. Będzie to wymagało wywołań DB z metody group.JoinUser, aby to sprawdzić.

Ale fakt, że Jednostka zależy od niektórych zewnętrznych usług / klas, nie wydaje mi się tak dobry i „naturalny”.

Jaki jest właściwy sposób radzenia sobie z tym w DDD?

Shaddix
źródło

Odpowiedzi:

15

Wyobraźmy sobie, że mam Grupy i Użytkowników, a gdy użytkownik chce dołączyć do grupy, dzwonię do metody groupsService.AddUserToGroup (grupa, użytkownik). W DDD powinienem zrobić group.JoinUser (użytkownik), który wygląda całkiem nieźle.

Ale DDD zachęca również do korzystania z usług (bezstanowych) do wykonywania zadań, jeśli dane zadanie jest zbyt złożone lub nie pasuje do modelu jednostki. Można mieć usługi w warstwie domeny. Ale usługi w warstwie domeny powinny obejmować wyłącznie logikę biznesową. Z drugiej strony zadania zewnętrzne i logika aplikacji (np. Wysyłanie wiadomości e-mail) powinny korzystać z usługi domeny w warstwie aplikacji, w której na przykład można mieć oddzielną usługę (aplikację) do jej pakowania.

Problem pojawia się, jeśli istnieją pewne reguły sprawdzania poprawności dotyczące dodawania użytkownika ...

Reguły sprawdzania poprawności należą do modelu domeny! Powinny być zamknięte w obiektach domeny (byty itp.).

... lub niektóre zadania zewnętrzne należy uruchomić, gdy użytkownik zostanie dodany do grupy. Wykonywanie tych zadań spowoduje, że jednostka będzie miała zewnętrzne zależności.

Chociaż nie wiem, o jakim zewnętrznym zadaniu mówisz, zakładam, że jest to coś w rodzaju wysłania wiadomości e-mail itp. Ale to nie jest tak naprawdę część twojego modelu domeny. Powinien żyć w warstwie aplikacji i być tam obsługiwany. W warstwie aplikacji możesz mieć usługę, która działa na usługach domenowych i jednostkach do wykonywania tych zadań.

Ale fakt, że Jednostka zależy od niektórych zewnętrznych usług / klas, nie wydaje mi się tak dobry i „naturalny”.

To jest nienaturalne i nie powinno się dziać. Podmiot nie powinien wiedzieć o rzeczach, za które nie jest odpowiedzialny. Usługi powinny być wykorzystywane do koordynowania interakcji między jednostkami.

Jaki jest właściwy sposób radzenia sobie z tym w DDD?

W twoim przypadku związek powinien być prawdopodobnie dwukierunkowy. To, czy użytkownik dołączy do grupy, czy grupa zabiera użytkownika, zależy od Twojej domeny. Czy użytkownik dołącza do grupy? Czy użytkownik został dodany do grupy? Jak to działa w Twojej domenie?

W każdym razie masz relację dwukierunkową, dzięki czemu możesz określić liczbę grup, do których użytkownik należy już w ramach agregacji użytkowników. To, czy przekażesz użytkownika do grupy, czy grupę do użytkownika, jest technicznie trywialne po ustaleniu klasy odpowiedzialnej.

Jednostka powinna następnie przeprowadzić walidację. Cała sprawa jest wywoływana z usługi warstwy aplikacji, która może również robić rzeczy techniczne, takie jak wysyłanie wiadomości e-mail itp.

Jeśli jednak logika sprawdzania poprawności jest naprawdę złożona, usługa domeny może być lepszym rozwiązaniem. W takim przypadku należy zawrzeć w nim reguły biznesowe, a następnie wywołać je z poziomu warstwy aplikacji.

Sokół
źródło
Ale jeśli przeniesiemy tyle logiki poza byt, co powinno być w środku?
SiberianGuy,
Bezpośrednie obowiązki podmiotu! Jeśli na przykład możesz powiedzieć „użytkownik może dołączyć do grupy”, jest to obowiązkiem użytkownika. Czasami musisz podejmować decyzje o kompromisie z przyczyn technicznych. Nie jestem też wielkim fanem relacji dwukierunkowych, ale czasem najlepiej pasuje do modelu. Słuchaj uważnie, gdy mówisz o domenie. „Istota robi ...” „Istota może ...” Kiedy słyszysz takie zdania, wtedy te operacje najprawdopodobniej należą do bytu.
Falcon,
Ponadto wiesz, że potrzebujesz usługi, gdy dwa lub więcej niepowiązanych ze sobą obiektów musi uczestniczyć w zadaniu, aby coś osiągnąć.
Falcon
1
Dzięki za odpowiedź, Falcon! Przy okazji, zawsze starałem się korzystać z usług bezstanowych, więc jestem o krok bliżej do DDD :) Powiedzmy, że w domenie ta operacja UserJoinsToGroup należy do grupy. Problem polega na tym, że aby zweryfikować tę operację, muszę wiedzieć, w ilu grupach użytkownik już uczestniczy (aby odrzucić operację, jeśli jest już> 3). Aby wiedzieć, że muszę wysłać zapytanie do bazy danych. Jak mogę to zrobić od podmiotu z grupy? Mam jeszcze kilka przykładów, kiedy muszę dotknąć DB w operacjach, które powinny naturalnie należeć do bytu (opublikuję je w razie potrzeby :))
Shaddix
2
Cóż, jeśli o tym pomyślę: co z podmiotem GroupMembership? Może być zbudowany przez fabrykę i ta fabryka może uzyskiwać dostęp do repozytoriów. To byłoby dobre DDD i podsumowuje tworzenie członkostwa. Fabryka może uzyskiwać dostęp do repozytoriów, tworzy członkostwo i dodaje je odpowiednio do użytkownika i grupy. Ten nowy byt mógłby również obejmować uprawnienia. Może to dobry pomysł.
Falcon,
3

Podejdę do problemu sprawdzania poprawności w następujący sposób: Utwórz usługę domenową o nazwie MembershipService:

class MembershipService : IMembershipService
{
   public MembershipService(IGroupRepository groupRepository)
   { 
     _groupRepository = groupRepository;
   }
   public int NumberOfGroupsAssignedTo(UserId userId)
   {
        return _groupsRepository.NumberOfGroupsAssignedTo(userId);
   }
}

Należy wstrzyknąć podmiotowi z Grupy IMemberShipService. Można to zrobić na poziomie klasy lub metody. Załóżmy, że robimy to na poziomie metody.

class Group{

   public void JoinUser(User user, IMembershipService membershipService)
   {
       if(membershipService.NumberOfGroupsAssignedTo(user.UserId) >= 3)
         throw new BusinessException("User assigned to more than 3 groups. Cannot proceed");

       // do some more stuff
   }
}

Usługa aplikacji: GroupServicemożna wstrzykiwać za IMemberShipServicepomocą wstrzykiwania konstruktora, który następnie można przekazać do JoinUsermetody Groupklasy.

Eklavya Gupta
źródło
1
Możesz rozważyć sformatowanie kodu źródłowego we wpisie, aby był bardziej czytelny
Benni,