2009-02-27 9 views
8

Attualmente sto refactoring del codice su un progetto che si sta concludendo e ho finito per inserire molta logica aziendale nelle classi di servizio piuttosto che negli oggetti di dominio. A questo punto la maggior parte degli oggetti di dominio sono solo contenitori di dati. Avevo deciso di scrivere la maggior parte della logica di business negli oggetti di servizio, e di rifattorizzare tutto in seguito in forme migliori, più riutilizzabili e più leggibili. In questo modo ho potuto decidere quale codice deve essere inserito negli oggetti del dominio e quale codice deve essere scorporato in nuovi oggetti e quale codice deve essere lasciato in una classe di servizio. Così ho qualche codice:Nella progettazione basata su domini, sarebbe una violazione di DDD mettere le chiamate ai repository di altri oggetti in un oggetto dominio?

public decimal CaculateBatchTotal(VendorApplicationBatch batch) 
{ 
    IList<VendorApplication> applications = AppRepo.GetByBatchId(batch.Id); 

    if (applications == null || applications.Count == 0) 
      throw new ArgumentException("There were no applications for this batch, that shouldn't be possible"); 
    decimal total = 0m; 
    foreach (VendorApplication app in applications) 
      total += app.Amount; 
    return total; 
} 

Questo codice sembra che sarebbe una buona aggiunta a un oggetto di dominio, perché è solo parametro di input è l'oggetto dominio stesso. Sembra un candidato perfetto per alcuni refactoring. Ma l'unico problema è che questo oggetto chiama il repository di un altro oggetto. Il che mi fa venir voglia di lasciarlo nella classe di servizio.

Le mie domande sono quindi:

  1. Dove ti inserire questo codice?
  2. Vuoi interrompere questa funzione?
  3. Dove lo metterebbe qualcuno che segue il rigoroso disegno Domain-Driven?
  4. Perché?

Grazie per il vostro tempo.

Modifica Nota: non è possibile utilizzare un ORM su questo, quindi non è possibile utilizzare una soluzione di caricamento lenta.

Modifica Nota2: Non riesco a modificare il costruttore per prendere i parametri, a causa di come il potenziale livello di dati istanzia gli oggetti del dominio utilizzando la riflessione (non è la mia idea).

Modifica Nota3: Non credo che un oggetto batch debba essere in grado di totalizzare solo un elenco di applicazioni, sembra che dovrebbe essere possibile solo per le applicazioni totali che si trovano in quel particolare batch. Altrimenti, ha più senso per me lasciare la funzione nella classe di servizio.

+0

Variabili locali con rivestimento Pascal. Che schifo. – yfeldblum

+0

@Justice, sento il tuo dolore – mbillard

risposta

5

Non si dovrebbe nemmeno avere accesso ai repository dall'oggetto dominio.

È possibile lasciare che il servizio fornisca all'oggetto dominio le informazioni appropriate o disponga di un delegato nell'oggetto dominio impostato da un servizio o nel costruttore.

public DomainObject(delegate getApplicationsByBatchID) 
{ 
    ... 
} 
+0

Buona idea, mi piace. –

+0

D'accordo, quindi per questa istanza farei il lavoro che richiede chiamate di repository nella classe di servizio, quindi passare una raccolta o un singolo oggetto all'entità se è necessario un calcolo –

+0

Sì, l'oggetto dominio userebbe il suo delegato per ottenere qualsiasi dato ha bisogno, il delegato sarà impostato da qualunque cosa crei l'oggetto dominio. Il delegato può essere direttamente un metodo di servizio o un metodo di repository. – mbillard

2

Perché non passare un IList <VendorApplication> come parametro anziché come VendorApplicationBatch? Il codice di chiamata per questo presumibilmente verrebbe da un servizio che avrebbe accesso all'AppRepo. In questo modo il tuo accesso al repository rimarrà al suo posto mentre la tua funzione di dominio può rimanere ignara beatitudine da dove provengono i dati.

+0

Se inserisco il codice che completa un batch nel repository, ma non il getbybatchid, non ci sarebbe garanzia che le applicazioni passate provengano da quel batch. Mi sembra di capire che batch dovrebbe essere in grado di totalizzare solo le applicazioni che contiene. –

+0

totalizza un batch nel repository -> ammonta a un batch nell'oggetto dominio batch –

+0

Non sono sicuro di aver capito. Quello che sto suggerendo è mantenere il tuo codice così com'è, eccetto che passare un VendorApplicationBatch che passi nella lista delle applicazioni. GetByBatchID è ancora nel repository, il codice che totalizza è ancora nel dominio. –

5

Non sono un esperto di DDD ma ricordo un articolo del grande Jeremy Miller che ha risposto a questa domanda per me. In genere si desidera la logica correlata agli oggetti del dominio, all'interno di tali oggetti, ma la classe del servizio eseguirà i metodi che contengono questa logica.Questo mi ha aiutato a spingere la logica specifica del dominio nelle classi di entità, e mantengo le mie classi di servizio meno ingombrante (come mi sono trovato mettendo a molta logica all'interno delle classi di servizio, come hai citato)

Edit: Esempio

I utilizzare la libreria impresa per la convalida semplice, quindi nella classe entità io impostare un attributo in questo modo:

[StringLengthValidator(1, 100)] 
public string Username { 
    get { return mUsername; } 
    set { mUsername = value; } 
} 

l'entità eredita da una classe di base che ha il seguente metodo "IsValid" che assicuri ogni oggetto incontra il criteri di convalida

 public bool IsValid() 
    { 
     mResults = new ValidationResults(); 
     Validate(mResults); 

     return mResults.IsValid(); 
    } 

    [SelfValidation()] 
    public virtual void Validate(ValidationResults results) 
    { 
     if (!object.ReferenceEquals(this.GetType(), typeof(BusinessBase<T>))) { 
      Validator validator = ValidationFactory.CreateValidator(this.GetType()); 
      results.AddAllResults(validator.Validate(this)); 
     } 
     //before we return the bool value, if we have any validation results map them into the 
     //broken rules property so the parent class can display them to the end user 
     if (!results.IsValid()) { 
      mBrokenRules = new List<BrokenRule>(); 
      foreach (Microsoft.Practices.EnterpriseLibrary.Validation.ValidationResult result in results) { 
       mRule = new BrokenRule(); 
       mRule.Message = result.Message; 
       mRule.PropertyName = result.Key.ToString(); 
       mBrokenRules.Add(mRule); 
      } 
     } 
    } 

Poi abbiamo bisogno di eseguire questo metodo di "IsValid" nella classe di servizio di salvataggio metodo, in questo modo:

public void SaveUser(User UserObject) 
{ 
    if (UserObject.IsValid()) { 
     mRepository.SaveUser(UserObject); 
    } 
} 

Un esempio più complesso potrebbe essere un conto bancario. La logica di deposito si troverà all'interno dell'oggetto account, ma la classe di servizio chiamerà questo metodo.

1

A quanto ho capito (informazioni insufficienti per sapere se questo è il progetto giusto) VendorApplicationBatch dovrebbe contenere un IList caricato pigro all'interno dell'oggetto dominio e la logica dovrebbe rimanere nel dominio.

per esempio (codice d'aria):

public class VendorApplicationBatch { 

    private IList<VendorApplication> Applications {get; set;}; 

    public decimal CaculateBatchTotal() 
    { 
     if (Applications == null || Applications.Count == 0) 
      throw new ArgumentException("There were no applications for this batch, that shouldn't be possible"); 

     decimal Total = 0m; 
     foreach (VendorApplication App in Applications) 
      Total += App.Amount; 
     return Total; 
    } 
} 

Questo è fatto facilmente con un ORM come NHibernate e penso che sarebbe la soluzione migliore.

+0

Sono d'accordo con te che questa è la soluzione migliore. Sfortunatamente non ero in giro quando è stata presa la decisione su come progettare il datalayer (o mancare lì), quindi non posso usare un ORM e ho creato un sistema di deposito kick-ass al suo posto. –

+0

Quindi la risposta è che il servizio ottenga l'elenco VendorApplication e che l'oggetto Batch faccia la logica. Esempio: VendorApplicationBatch.CaculateBatchTotal (applicazioni IList ) – gcores

+0

Non penso sia corretto consentire a un batch di completare le applicazioni che potrebbero appartenere a un altro batch, ci deve essere un modo per garantire che un batch possa totalizzare solo il proprio fornitore applicazioni. Quindi non posso in buona fede, inserire questa funzione nel batch. –

0

Mi sembra che il vostro CalculateTotal è un servizio per le collezioni di VendorApplication di, e che il ritorno alla raccolta di VendorApplication di un batch si inserisce naturalmente come una proprietà della classe batch. Pertanto, altri servizi/controller/qualsiasi altro recupererebbero la raccolta appropriata di VendorApplication da un batch e passarli al servizio VendorApplicationTotalCalculator (o qualcosa di simile). Ma questo potrebbe infrangere alcune regole del servizio di root aggregato DDD o qualcosa di cui sono ignorante (novizio di DDD).

Problemi correlati