Domanda

Ho letto alcune delle domande riguardanti i modelli di dominio anemico e la separazione delle preoccupazioni. Quali sono le migliori tecniche per eseguire / collegare la logica di dominio su oggetti di dominio anemici? Nel mio lavoro, abbiamo un modello piuttosto anemico e attualmente stiamo usando " helper " classi per eseguire il database / business logic sugli oggetti dominio. Ad esempio:

public class Customer
{
    public string Name {get;set;}
    public string Address {get;set;}
}

public class Product
{
    public string Name {get;set;}
    public decimal Price {get;set;}
}

public class StoreHelper
{
    public void PurchaseProduct(Customer c, Product p)
    {
         // Lookup Customer and Product in db
         // Create records for purchase
         // etc.
    }
}

Quando l'app deve effettuare un acquisto, crea StoreHelper e chiama il metodo sugli oggetti dominio. Per me, avrebbe senso per il cliente / prodotto sapere come salvarsi in un repository, ma probabilmente non vorrai i metodi Save () sugli oggetti del dominio. Avrebbe anche senso per un metodo come Customer.Purchase (Prodotto), ma che sta mettendo la logica di dominio sull'entità.

Ecco alcune tecniche che ho incontrato, non sono sicuro che siano buone / cattive:

  1. Il cliente e il prodotto ereditano da un'entità " classe, che fornisce le operazioni CRUD di base in modo generico (usando forse un ORM).
    • Pro: ogni oggetto dati otterrebbe automaticamente le operazioni CRUD, ma verrà quindi collegato al database / ORM
    • Contro: questo non risolve il problema delle operazioni di business sugli oggetti e lega anche tutti gli oggetti di dominio a un'entità di base che potrebbe non essere appropriata
  2. Utilizzare le classi di supporto per gestire le operazioni CRUD e la logica aziendale
    • Ha senso avere DAO per il "puro database" operazioni e aiutanti aziendali separati per le operazioni più specifiche per l'azienda?
    • È meglio usare classi di supporto statiche o non statiche per questo?
    • Pro: gli oggetti di dominio non sono legati a nessuna logica di database / business (completamente anemica)
    • Contro: non molto OO, non molto naturale usare gli helper nel codice dell'applicazione (sembra un codice C)
  3. Utilizzare la tecnica Double Dispatch in cui l'entità ha metodi per salvare in un repository arbitrario
    • Pro: migliore separazione delle preoccupazioni
    • Contro: le entità hanno qualche logica aggiuntiva collegata (sebbene sia disaccoppiata)
  4. In C # 3.0, è possibile utilizzare i metodi di estensione per collegare i metodi CRUD / business a un oggetto di dominio senza toccarlo
    • È un approccio valido? Cosa sono i pro / contro?
  5. Altre tecniche?

Quali sono le migliori tecniche per gestirlo? Sono abbastanza nuovo in DDD (sto leggendo il libro di Evans - quindi forse questo mi aprirà gli occhi)

È stato utile?

Soluzione

Martin Fowler ha scritto molto sui modelli di dominio, tra cui modelli di dominio anemico . Ha anche brevi descrizioni (e diagrammi di classe UML) di molti modelli di progettazione per modelli di dominio e database che potrebbero essere utili: Catalogo di " ; Patterns of Enterprise Application Architecture " .

Suggerirei di guardare Record attivo e Mappatore dati . Dalla descrizione del tuo problema, sembra che le tue classi di supporto contengano dettagli sull'implementazione del database e di regole dominio / business.

Il Record attivo sposta la logica di dominio dell'helper e il codice del database negli altri oggetti di dominio (come la classe di base Entity ). Data Mapper sposta la logica di dominio dell'helper negli oggetti dominio e il codice del database in un oggetto mappa separato. Entrambi gli approcci sarebbero più orientati agli oggetti rispetto alle classi di supporto in stile procedurale.

Eric Evans "" Domain Driven Design " il libro è eccellente. Si secca un po ', ma ne vale sicuramente la pena. InfoQ ha un " Domain Driven Design rapidamente " mini-libro che è una buona introduzione al libro di Evans. Più "quotazione guidata dal dominio rapidamente" è disponibile come PDF gratuito.

Altri suggerimenti

Al fine di evitare il modello anemico, riformatta le tue classi di aiuto:

Logica come:
" Customer.PurchaseProduct (Prodotto prodotto, Pagamento pagamento) " ;,
" Customer.KillCustomer (Person killer, Weapon arma) "
dovrebbe esistere direttamente in " Customer " oggetto di dominio.

Logica come:
& Quot; Customer.IsCustomerAlive () "
& Quot; Customer.IsCustomerHappy () "
dovrebbe andare alle specifiche.

Logica come:
& Quot; Customer.Create () " ;,
& Quot; Customer.Update () "
ovviamente dovrebbe andare ai repository.

Logica come:
& Quot; Customer.SerializeInXml () "
" Customer.GetSerializedCustomerSizeInBytes () "
dovrebbe andare ai servizi.

I costruttori complessi dovrebbero andare nelle fabbriche.

Ecco come lo vedo. Sarei felice se qualcuno potesse commentare la mia comprensione dell'approccio DDD.


Modifica

A volte - modello di dominio anemico non dovrebbe essere evitato .

Modificata la mia risposta per aggiungere che DDD non riguarda la raccolta e la caduta di schemi.
DDD riguarda il modo in cui pensiamo.

Ho sempre pensato al modello di dominio anemico come un anti pattern. È chiaro che un cliente acquisterà prodotti, tale capacità può essere generata da un'implementazione dell'interfaccia

Interface IPurchase
      Purchase(Product);

, quindi uno qualsiasi dei tuoi oggetti di dominio può quindi implementarlo come richiesto. In questo modo puoi introdurre funzionalità nei tuoi oggetti di dominio, che è esattamente dove dovrebbe essere.

Un approccio che non hai menzionato è l'utilizzo dell'AOP per gestire l'accesso ai dati. Un esempio del mio recente utilizzo di questo approccio (sebbene ampiamente semplificato a fini di pubblicazione) è che avevo un'entità di dominio Account che aveva un metodo debit , incapsulando la logica aziendale richiesta per effettuare un addebito corretto dall'account.

NB. Tutto il codice è Java con notazione AOP AspectJ ...

public boolean debit(int amount) {
    if (balance - amount >= 0) {
        balance = balance - amount;
        return true;
    }
    return false;
}

Con il repository appropriato iniettato nel mio aspetto, ho quindi usato un punto di riferimento per intercettare le chiamate a questo metodo ...

pointcut debit(Account account,int amount) :
    execution(boolean Account.debit(int)) &&
    args(amount) &&
    target(account);

... e applicato alcuni consigli:

after(Account account, int amount) returning (boolean result)  : debit(account,amount) {
    if (result) getAccountRepository().debit(account, amount);
}

A mio avviso, ciò offre una buona separazione delle preoccupazioni e consente alle entità del dominio di concentrarsi interamente sulla logica aziendale dell'applicazione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top