Repozytoria DDD w aplikacji lub domenie

29

Obecnie studiuję DDD i mam pytania dotyczące zarządzania repozytoriami za pomocą DDD.

Właściwie spotkałem dwie możliwości:

Pierwszy

Pierwszym sposobem zarządzania usługami, które przeczytałem, jest wstrzyknięcie repozytorium i modelu domeny do usługi aplikacji.

W ten sposób, w jednej z metod usługi aplikacji, wywołujemy metodę usługi domeny (sprawdzanie reguł biznesowych), a jeśli warunek jest dobry, repozytorium jest wywoływane specjalną metodą w celu utrwalenia / odzyskania jednostki z bazy danych.

Prostym sposobem na zrobienie tego może być:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

Drugi

Drugą możliwością jest wstrzyknięcie repozytorium do wnętrza usługi domainService i korzystanie z repozytorium tylko za pośrednictwem usługi domeny:

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

Odtąd nie jestem w stanie rozróżnić, który jest najlepszy (jeśli jest najlepszy) lub co im to sugeruje w swoim kontekście.

Czy możesz podać mi przykład, gdzie jedno może być lepsze od drugiego i dlaczego?

mfrachet
źródło
„aby wstrzyknąć repozytorium i model domeny do usługi aplikacji”. Co masz na myśli, mówiąc gdzieś „model domeny”? AFAICT pod względem modelu domeny DDD oznacza cały zestaw pojęć z dziedziny i interakcji między nimi, które są istotne dla aplikacji. To abstrakcyjna rzecz, to nie jakiś obiekt w pamięci. Nie możesz tego wstrzyknąć.
Alexey

Odpowiedzi:

31

Krótka odpowiedź brzmi - możesz używać repozytoriów z usługi aplikacji lub usługi domeny - ale ważne jest, aby zastanowić się, dlaczego i jak to robisz.

Cel usługi domenowej

Usługi domenowe powinny zawierać koncepcje / logikę domeny - jako taką, metoda usługi domenowej:

domainService.persist(data)

nie należy do usługi domenowej, ponieważ persistnie jest częścią wszechobecnego języka, a operacja trwałości nie jest częścią logiki biznesowej domeny.

Zasadniczo usługi domenowe są przydatne, gdy masz reguły / logikę biznesową, które wymagają koordynacji lub pracy z więcej niż jednym agregatem. Jeśli logika dotyczy tylko jednego agregatu, powinna być w metodzie na jednostkach tego agregatu.

Repozytoria w usługach aplikacyjnych

W tym sensie, w twoim przykładzie, wolę twoją pierwszą opcję - ale nawet tam jest miejsce na ulepszenia, ponieważ twoja usługa domeny przyjmuje surowe dane z interfejsu API - dlaczego usługa domeny powinna wiedzieć o strukturze data? Ponadto wydaje się, że dane są powiązane tylko z pojedynczym agregatem, więc korzystanie z usługi domenowej jest ograniczone - generalnie umieszczam walidację w konstruktorze encji. na przykład

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

i wrzuć wyjątek, jeśli jest nieważny. W zależności od frameworka aplikacji, może być łatwo mieć spójny mechanizm wychwytywania wyjątku i mapowania go na odpowiednią odpowiedź dla typu interfejsu API - np. Dla interfejsu API REST, zwróć kod statusu 400.

Repozytoria w usługach domenowych

Niezależnie od powyższego, czasami przydatne jest wstrzykiwanie i używanie repozytorium w usłudze domenowej, ale tylko wtedy, gdy repozytoria są zaimplementowane w taki sposób, że akceptują i zwracają tylko zagregowane katalogi główne, a także tam, gdzie abstrakcyjna jest logika obejmująca wiele agregatów. na przykład

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

implementacja usługi domeny wyglądałaby następująco:

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

Wniosek

Kluczem jest tutaj to, że usługa domeny zawiera w sobie proces, który jest częścią wszechobecnego języka. Aby wypełnić swoją rolę, musi korzystać z repozytoriów - i jest to całkowicie w porządku.

Ale dodanie usługi domeny, która otacza repozytorium metodą zwaną, nie persistma żadnej wartości.

Na tej podstawie, jeśli usługa aplikacji wyraża przypadek użycia, który wymaga tylko pracy z jednym agregatem, nie ma problemu z użyciem repozytorium bezpośrednio z usługi aplikacji.

Chris Simon
źródło
Okej, więc jeśli mam reguły biznesowe (dopuszczając reguły wzorca specyfikacji), jeśli dotyczy to tylko jednego podmiotu, powinienem sprawdzić poprawność w tym obiekcie? Dziwnie wydaje się wprowadzanie reguł biznesowych, takich jak kontrolowanie dobrego formatu poczty użytkownika wewnątrz jednostki użytkownika. Czyż nie Dziękuję za globalną odpowiedź. Stwierdzono, że nie ma „domyślnej reguły do ​​zastosowania” i tak naprawdę zależy od naszych przypadków użycia. Mam trochę pracy, aby dobrze rozróżnić całą tę pracę
mfrachet,
2
Aby wyjaśnić, reguły należące do encji są tylko regułami, za które odpowiedzialny jest ten encja. Zgadzam się, kontrolowanie dobrego formatu wiadomości e-mail użytkownika nie wydaje się, że należy on do encji użytkownika. Osobiście lubię umieszczać takie reguły sprawdzania poprawności w obiekcie wartości reprezentującym adres e-mail. Użytkownik miałby właściwość typu EmailAddress, a konstruktor EmailAddress akceptuje ciąg znaków i zgłasza wyjątek, jeśli ciąg nie jest zgodny z wymaganym formatem. Następnie możesz ponownie użyć parametru EmailAddress ValueObject na innych podmiotach, które muszą przechowywać adres e-mail.
Chris Simon
Ok, rozumiem, dlaczego warto teraz korzystać z Value Object. Ale to oznacza, że ​​obiekt wartości ma właściwość, która jest regułą biznesową zarządzającą formatem?
mfrachet
1
Obiekty wartości powinny być niezmienne. Ogólnie oznacza to, że inicjujesz i sprawdzasz poprawność w konstruktorze, a dla wszystkich właściwości używaj publicznego wzorca get / private set. Ale możesz użyć konstrukcji języka do zdefiniowania równości, procesu ToString itp. Np. Kacper.gunia.me/ddd-building-blocks-in-php-value-object lub github.com/spring-projects/spring-gemfire-examples/ blob / master /…
Chris Simon
Dziękuję @ChrisSimon, wreszcie i odpowiedz na rzeczywistą sytuację DDD, która dotyczy kodu, a nie tylko teorii. Poświęciłem 5 dni na przeglądanie SO i sieci jako funkcjonalny przykład tworzenia i zapisywania agregatu, i to jest najbardziej jasne wytłumaczenie, jakie znalazłem.
e_i_pi,
2

Wystąpił problem z zaakceptowaną odpowiedzią:

Model domeny nie może zależeć od repozytorium, a usługa domeny jest częścią modelu domeny -> usługa domeny nie powinna zależeć od repozytorium.

Zamiast tego należy zgromadzić wszystkie jednostki, które są potrzebne do wykonania logiki biznesowej już w usłudze aplikacji, a następnie po prostu dostarczyć swoim modelom obiekty instancji.

W oparciu o twój przykład może to wyglądać następująco:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

Ogólna zasada: model domeny nie zależy od zewnętrznych warstw

Aplikacja a usługa domenowa Z tego artykułu :

  • Usługi domenowe są bardzo szczegółowe, ponieważ jako usługi aplikacyjne mają na celu zapewnienie interfejsu API.

  • Usługi domenowe zawierają logikę domeny, której naturalnie nie można umieścić w encji lub obiekcie wartości, podczas gdy usługi aplikacji sterują wykonywaniem logiki domeny i same nie implementują żadnej logiki domeny.

  • Metody usług domenowych mogą mieć inne elementy domeny, takie jak argumenty i zwracane wartości, podczas gdy usługi aplikacji działają na prostych argumentach, takich jak wartości tożsamości i prymitywne struktury danych.

  • Usługi aplikacji deklarują zależności od usług infrastrukturalnych wymaganych do wykonania logiki domeny.

sMs
źródło
1

Żaden z twoich wzorców nie jest dobry, chyba że twoje usługi i przedmioty zawierają pewien spójny zestaw odpowiedzialności.

Najpierw powiedz, jaki jest Twój obiekt domeny i porozmawiaj o tym, co może zrobić w języku domeny. Jeśli może być poprawne lub niepoprawne, dlaczego nie mieć tego jako właściwości samego obiektu domeny?

Jeśli na przykład ważność obiektów ma sens tylko w odniesieniu do innego obiektu, być może masz odpowiedzialność za „regułę sprawdzania poprawności X dla obiektów domeny”, która może być zawarta w zestawie usług.

Czy sprawdzanie poprawności obiektu wymaga przechowywania go w ramach reguł biznesowych? Prawdopodobnie nie. Odpowiedzialność za „przechowywanie obiektów” zwykle spoczywa w oddzielnym obiekcie repozytorium.

Teraz masz operację, którą chcesz wykonać, która obejmuje zakres obowiązków, stwórz obiekt, zweryfikuj go i jeśli jest ważny, zapisz go.

Czy ta operacja jest nieodłączna od obiektu domeny? Następnie uczyń go częścią tego obiektu, tjExamQuestion.Answer(string answer)

Czy pasuje do innej części Twojej domeny? połóż to tamBasket.Purchase(Order order)

Wolisz robić usługi ADM REST? Ok, więc.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Ewan
źródło