Se si è costretti a utilizzare un modello di dominio anemico, dove si fa a mettere la vostra logica di business e campi calcolati?

StackOverflow https://stackoverflow.com/questions/1933351

Domanda

La nostra attuale strumento di O / RM in realtà non consentono di modelli di dominio ricchi, quindi siamo costretti a utilizzare le entità anemici (DTO) in tutto il mondo. Questo ha funzionato bene, ma io continuo a lottare con dove mettere la logica di business basato su oggetti di base e campi calcolati.

strati correnti:

  • Presentazione
  • servizio
  • Repository
  • Dati / Entità

Il nostro strato repository ha la maggior parte della base Fetch / validate / salvare la logica, anche se il livello di servizio fa un sacco di più complesso di convalida e di risparmio (in quanto le operazioni di salvataggio anche fare la registrazione, il controllo dei permessi, ecc). Il problema è dove mettere il codice in questo modo:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

o

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

Qualche idea?

È stato utile?

Soluzione

Torniamo alle origini:

servizi

Servizi sono disponibili in 3 gusti: Servizi di dominio , Application Services e Infrastrutture

  • Servizi di dominio : Incapsula logica di business che non fa naturalmente inserirsi all'interno di un oggetto di dominio. Nel tuo caso, tutti della vostra logica di business.
  • servizi per le applicazioni : Utilizzato dai consumatori esterni di parlare con il vostro sistema
  • Infrastrutture : Utilizzato per problemi tecnici astratti (ad esempio MSMQ, provider di posta elettronica, ecc)

Repository

Questo è dove il vostro controlli di coerenza dei dati di accesso e andare. In puro DDD, il tuo radici di aggregazione sarebbe responsabile per la verifica di coerenza (prima persistente qualsiasi oggetto). Nel tuo caso, si usa assegni dal Servizi di dominio livello.


Soluzione proposta: dividere il servizi esistenti a parte

utilizzare un nuovo Servizi di dominio Livello per incapsulare tutta la logica per i vostri DTOs, e il vostro controlli di coerenza troppo (utilizzando Specifiche , forse?).

Utilizzare la Application Service per esporre il necessario prendere metodi (FetchOpenOrdersWithLines), che trasmettere le richieste al Repository (e l'uso dei farmaci generici, come suggerito Jeremy). Si potrebbe anche considerare l'utilizzo di specifiche della query per avvolgere le vostre domande.

Dal tuo Repository , usare Specifiche nella Servizi di dominio strato di verificare la consistenza oggetto etc prima persistere gli oggetti.

Si possono trovare supporto informazioni nel libro di Evans':

  • "Servizi e lo strato di dominio isolato" (pag 106)
  • "Specifiche" (pag 224)
  • "Specifiche query" (pag 229)

Altri suggerimenti

Sono tentato di rispondere a Mu , ma mi piacerebbe elaborare . In sintesi: Non lasciare che la vostra scelta di ORM dettare come si definisce il modello di dominio

.

Lo scopo del modello di dominio è quello di essere una ricca API orientata agli oggetti che i modelli di dominio. Per seguire vero Domain-Driven Design , il modello di dominio deve essere definito non vincolata dalla tecnologia .

In altre parole, la modello di dominio viene prima , e tutte le implementazioni specifiche tecnologie vengono successivamente indirizzati da mapper che mappa tra il modello di dominio e la tecnologia in questione. Ciò spesso includono entrambi i modi:. Per lo strato di accesso ai dati in cui la scelta di ORM può introdurre vincoli, e allo strato di interfaccia utente in cui la tecnologia di interfaccia utente impone requisiti aggiuntivi

Se l'implementazione è straordinariamente lontano dal modello di dominio, si parla di un anticorruzione Strato .

Nel tuo caso, quello che si chiama un modello di dominio anemico è in realtà il Data Access Layer. Il vostro ricorso migliore sarebbe quella di definire la Repositories che modello di accesso ai vostri Entità in modo tecnologicamente neutrale.

Per fare un esempio, diamo un'occhiata a vostro Ordine entità. Modellazione un Ordine non costretto dalla tecnologia ci potrebbe portare a qualcosa di simile:

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}

Si noti che questo è un oggetto Plain Old CLR ( POCO ) ed è quindi vincolata dalla tecnologia. Ora la domanda è come si ottiene questo dentro e fuori del tuo archivio dati?

Questo dovrebbe essere fatto tramite un IOrderRepository astratto:

public interface IOrderRepository
{
    Order SelectSingle(int id);

    void Insert(Order order);

    void Update(Order order);

    void Delete(int id);

    // more, specialized methods can go here if need be
}

È ora possibile implementare IOrderRepository usando il vostro ORM di scelta. Tuttavia, alcuni ORM (come Entity Framework di Microsoft) richiede di derivare le classi di dati da determinate classi di base, quindi questo non va bene a tutti con dominio oggetti come pocos. Perciò, è necessario mappatura.

La cosa importante da capire è che si può avere fortemente tipizzato classi di dati che semanticamente assomigliare tuoi entità del dominio. Tuttavia, questo è un dettaglio di implementazione puro, in modo da non confondersi con quella. Una classe Order che deriva da esempio EntityObject non è un dominio di classe - è un dettaglio di implementazione, in modo che quando si implementa IOrderRepository, è necessario mappare l'ordine classe di dati per l'ordine Doman classe

.

Questo può essere un lavoro noioso, ma è possibile utilizzare automapper di farlo per voi.

Ecco come un'implementazione del metodo SelectSingle potrebbe essere:

public Order SelectSinge(int id)
{
    var oe = (from o in this.objectContext.Orders
              where o.Id == id
              select o).First();
    return this.mapper.Map<OrderEntity, Order>(oe);
}

Questo è esattamente ciò che il livello di servizio è per - Ho visto anche le applicazioni in cui è chiamato lo strato BusinessLogic.

Queste sono le routine si vorrà passare la maggior parte del vostro tempo di prova, e se sono nel loro strato poi beffardo lo strato repository dovrebbe essere semplice.

Lo strato repository dovrebbe essere genericized il più possibile, quindi non è un luogo adatto per la logica di business che è individuo a classi particolari.

Da quello che dici può essere che stai pensando troppo rigidamente circa il vostro servizio e gli strati Repository. Sembra che tu non vuoi la parte di presentazione di avere una dipendenza diretta sullo strato repository e per raggiungere questo obiettivo si sta duplicando metodi dai repository (i metodi di pass-through) nello strato di servizio.

Vorrei mettere in discussione questo. Si potrebbe rilassarsi e permettere che sia per essere utilizzato entro la parte di presentazione e rendere la vita più semplice per un inizio. Forse chiedere la vostra auto che cosa il vostro raggiungimento nascondendo i repository del genere. Stai già astraendo la persistenza e l'interrogazione ATTUAZIONE con loro. Questo è grande e ciò che essi sono progettati per. Sembra come se si sta cercando di creare un livello di servizio che nasconde il fatto proprie entità vengono mantenute a tutti. Mi piacerebbe chiedere perché?

Per quanto riguarda il calcolo totali degli ordini ecc tuo livello di servizio sarebbe la sede naturale. Una classe SalesOrderCalculator con LineTotal (LineItem LineItem) e OrderTotal (minimo d'ordine) i metodi andrebbe bene. Si può anche prendere in considerazione la creazione di una fabbrica appropriata per esempio OrderServices.CreateOrderCalculator () per passare l'applicazione se necessario (tassa sui acquista ha regole specifiche per paese, per esempio). Questo potrebbe anche formare un unico punto di ingresso per ordinare i servizi e rendere le cose facili trovare attraverso IntelliSense.

Se tutto questo suona impraticabile può essere è necessario pensare più profondamente su ciò che i vostri astrazioni stanno ottenendo, come si relazionano tra loro e la Responsabilità singolo Principio. Un repository è un'astrazione infrastrutture (nascondendo COME entità vengono salvati e recuperati). Servizi astrarre l'implementazione delle azioni di business o regole e permettono una migliore struttura per il controllo delle versioni o varianza. Essi non sono generalmente stratificato nel modo che descrivi. Se si dispone di regole di sicurezza complesse nei vostri servizi, i repository può essere la casa migliore. In un tipico modello di stile DDD, Depositi, Enti, oggetti di valore e servizi sarebbero stati tutti utilizzati lungo il lato l'altro nello stesso livello e come parte dello stesso modello. Livelli superiori (in genere presentazione) sarebbero quindi essere isolate da queste astrazioni. All'interno del modello l'attuazione di un servizio può utilizzare l'astrazione di un altro. Un ulteriore affinamento aggiunge regole a chi detiene i riferimenti a cui le entità o gli oggetti di valore far rispettare un contesto più formale del ciclo di vita. Per ulteriori informazioni su questo mi sento di raccomandare a studiare il Eric Evans libro o Domain Driven design rapidamente .

Se la tua tecnologia ORM gestisce solo gli oggetti DTO bene, che non significa che devi buttare via ricchi oggetti entità. È ancora possibile avvolgere gli oggetti DTO con oggetti entità:

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}

Ho trovato il nuovo libro di Dino Esposito Microsoft® .NET: Le domande progettare per il Enterprise di essere un grande repository di conoscenza per questi tipi di domande e problemi.

Il livello di servizio.

Se si desidera aggiungere un po 'di comportamento alle vostre entità, ma non è possibile modificare le entità, i metodi di estensione dare una prova. Mi piacerebbe farlo solo per gli scenari semplici, come nel tuo esempio però. Qualcosa di più complesso o che coordina tra diversi enti e / o servizi, livelli, o qualsiasi altra cosa dovrebbe essere in un servizio di dominio come suggerito già.

Per esempio (dai vostri esempi):

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

Se ci sono molto pochi di queste aggiunte che si desidera, si può semplicemente metterli tutti in una classe "DomainExtensions", ma mi piacerebbe comunque suggerisco trattandoli con rispetto regolare e mantenere tutte le estensioni di un'entità in una classe in il proprio file.

FYI: L'unica volta che ho fatto questo è quando ho avuto una soluzione L2S e non volevo fare confusione con i parziali. Anche io non ho avuto molte estensioni perché la soluzione era piccolo. Mi piace l'idea di andare con un pieno soffiato strato di servizi di dominio migliore.

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