Repository DDD nell'applicazione o nel servizio di dominio


29

In questi giorni sto studiando DDD e ho alcune domande su come gestire i repository con DDD.

In realtà, ho incontrato due possibilità:

Il primo

Il primo modo di gestire i servizi che ho letto è quello di iniettare un repository e un modello di dominio in un servizio applicativo.

In questo modo, in uno dei metodi di servizio dell'applicazione, chiamiamo un metodo di servizio di dominio (controllo delle regole aziendali) e, se la condizione è buona, il repository viene chiamato su un metodo speciale per mantenere / recuperare l'entità dal database.

Un modo semplice per farlo potrebbe essere:

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))
    }
    // ...
  }

}

Il secondo

La seconda possibilità è quella di iniettare invece il repository all'interno di domainService e di utilizzare il repository solo tramite il servizio di dominio:

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
  }

}

Da ora, non sono in grado di distinguere quale è il migliore (se ce n'è uno migliore) o cosa implicano entrambi nel loro contesto.

Potete darmi un esempio di dove uno potrebbe essere migliore dell'altro e perché?



"per iniettare un repository e un modello di dominio in un servizio applicativo." Cosa intendi con l'iniezione di un "modello di dominio" da qualche parte? AFAICT in termini di modello di dominio DDD indica l'intera serie di concetti del dominio e le interazioni tra di essi rilevanti per l'applicazione. È una cosa astratta, non è un oggetto in memoria. Non puoi iniettarlo.
Alexey,

Risposte:


31

La risposta breve è - è possibile utilizzare i repository da un servizio applicativo o da un servizio di dominio - ma è importante considerare perché e come lo si sta facendo.

Scopo di un servizio di dominio

I servizi di dominio dovrebbero incapsulare concetti / logica del dominio - come tale, il metodo del servizio di dominio:

domainService.persist(data)

non appartiene a un servizio di dominio, in quanto persistnon fa parte del linguaggio onnipresente e l'operazione di persistenza non fa parte della logica aziendale del dominio.

In genere, i servizi di dominio sono utili quando si hanno regole / logiche aziendali che richiedono il coordinamento o la collaborazione con più di un aggregato. Se la logica coinvolge solo un aggregato, dovrebbe essere in un metodo sulle entità di quell'aggregato.

Archivi in ​​Application Services

Quindi, in questo senso, nel tuo esempio, preferisco la tua prima opzione - ma anche c'è spazio per miglioramenti, dato che il tuo servizio di dominio accetta i dati grezzi dall'API - perché il servizio di dominio dovrebbe conoscere la struttura di data?. Inoltre, i dati sembrano essere correlati solo a un singolo aggregato, quindi esiste un valore limitato nell'uso di un servizio di dominio per questo - in genere inserirei la convalida all'interno del costruttore dell'entità. per esempio

postAction(data){

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

  this.repository.persist(entity);

  // ...
}

e genera un'eccezione se non è valida. A seconda del framework dell'applicazione, potrebbe essere semplice disporre di un meccanismo coerente per rilevare l'eccezione e mapparla sulla risposta appropriata per il tipo di api, ad esempio per un api REST, restituire un codice di stato 400.

Archivi nei servizi di dominio

Nonostante quanto sopra, a volte è utile iniettare e usare un repository in un servizio di dominio, ma solo se i tuoi repository sono implementati in modo tale da accettare e restituire solo le radici aggregate, e anche dove stai astraggendo la logica che coinvolge più aggregati. per esempio

postAction(data){

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

  // ...
}

l'implementazione del servizio di dominio sarebbe simile a:

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);

}

Conclusione

La chiave qui è che il servizio di dominio incapsula un processo che fa parte del linguaggio onnipresente. Per adempiere al suo ruolo, deve utilizzare i repository - ed è perfettamente bene farlo.

Ma l'aggiunta di un servizio di dominio che avvolge un repository con un metodo chiamato persistaggiunge poco valore.

Su tale base, se il servizio applicativo sta esprimendo un caso d'uso che richiede di lavorare solo con un singolo aggregato, non vi è alcun problema nell'utilizzare il repository direttamente dal servizio applicativo.


Va bene, quindi se ho regole di business (ammettendo le regole del modello di specifica), se riguarda solo un'entità, dovrei ma la convalida in quell'entità? Sembra strano iniettare regole di business come controllare un buon formato di posta utente all'interno dell'entità utente. No? Per quanto riguarda la risposta globale, grazie. Ha scoperto che non esiste una "regola predefinita da applicare" e dipende davvero dalle nostre cartelle di utilizzo. Ho del lavoro da fare per distinguere bene tutto questo lavoro
mfrachet,

2
Per chiarire, le regole che appartengono all'entità sono solo le regole che sono di responsabilità di tale entità. Sono d'accordo, il controllo di un buon formato e-mail dell'utente non sembra appartenere all'entità Utente. Personalmente, mi piace inserire regole di convalida del genere in un oggetto valore che rappresenti un indirizzo e-mail. L'utente avrebbe una proprietà di tipo EmailAddress e il costruttore EmailAddress accetta una stringa e genera un'eccezione se la stringa non corrisponde al formato richiesto. Quindi è possibile riutilizzare l'oggetto valore EmailAddress su altre entità che devono memorizzare un indirizzo e-mail.
Chris Simon,

Bene, vedo perché usare Value Object ora. Ma significa che l'oggetto valore deve una proprietà che è la regola aziendale che gestisce il formato?
mfrachet,

1
Gli oggetti valore dovrebbero essere immutabili. In genere questo significa che si inizializza e si convalida nel costruttore e per qualsiasi proprietà si utilizza il modello di set pubblico get / private. Ma puoi usare i costrutti del linguaggio per definire l'uguaglianza, il processo ToString, ecc. Es. Kacper.gunia.me/ddd-building-blocks-in-php-value-object o github.com/spring-projects/spring-gemfire-examples/ blob / master /…
Chris Simon,

Grazie @ChrisSimon, finalmente e rispondi a una situazione DDD nella vita reale che coinvolge il codice e non solo la teoria. Ho trascorso 5 giorni a cercare SO e il web per un esempio funzionale di creazione e salvataggio di un aggregato, e questa è la spiegazione più chiara che ho trovato.
e_i_pi,

2

Si è verificato un problema con la risposta accettata:

Il modello di dominio non può dipendere dal repository e il servizio di dominio fa parte del modello di dominio -> il servizio di dominio non dovrebbe dipendere dal repository.

Quello che dovresti fare invece è assemblare tutte le entità che sono necessarie per l'esecuzione della logica aziendale già nel servizio dell'applicazione e quindi fornire ai tuoi modelli oggetti istanziati.

Sulla base del tuo esempio potrebbe apparire così:

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))
    }
    // ...
  }
}

Quindi, regola empirica: il modello di dominio non dipende dai livelli esterni

Servizio applicazione vs dominio Da questo articolo :

  • I servizi di dominio sono molto granulari dove i servizi applicativi sono una facciata finalizzata a fornire un'API.

  • I servizi di dominio contengono una logica di dominio che non può essere naturalmente inserita in un'entità o in un oggetto valore mentre i servizi dell'applicazione orchestrano l'esecuzione della logica di dominio e non implementano essi stessi alcuna logica di dominio.

  • I metodi di servizio di dominio possono avere altri elementi di dominio come operandi e restituire valori mentre i servizi di applicazione operano su banali operandi come valori di identità e strutture di dati primitivi.

  • I servizi applicativi dichiarano dipendenze dai servizi infrastrutturali richiesti per eseguire la logica di dominio.


1

Nessuno dei tuoi schemi è buono a meno che i tuoi servizi e oggetti incapsulino un insieme coerente di responsabilità.

Prima di tutto dì qual è il tuo oggetto di dominio e parla di cosa può fare nella lingua del dominio. Se può essere valido o non valido, perché non avere questo come proprietà dell'oggetto dominio stesso?

Se ad esempio la validità degli oggetti ha senso solo in termini di un altro oggetto, allora forse hai una responsabilità "regola di convalida X per oggetti di dominio" che può essere incapsulata in un insieme di servizi.

La convalida di un oggetto richiede la sua memorizzazione all'interno delle regole aziendali? Probabilmente no. La responsabilità di "immagazzinare oggetti" normalmente rientra in un oggetto repository separato.

Ora hai un'operazione che desideri eseguire che copre una serie di responsabilità, crea un oggetto, lo convalida e, se valido, lo memorizza.

Questa operazione è intrinseca all'oggetto dominio? Quindi rendilo parte di quell'oggetto, ad esExamQuestion.Answer(string answer)

Si adatta a qualche altra parte del tuo dominio? mettilo lìBasket.Purchase(Order order)

Preferiresti fare i servizi ADM REST? Va bene allora.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.