Si vous êtes obligé d'utiliser un modèle de domaine anémiques, où mettez-vous votre logique métier et les champs calculés?

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

Question

Notre outil actuel O / RM ne permet pas vraiment de modèles riches de domaine, donc nous sommes obligés d'utiliser des entités anémiques (DTO) partout. Cela a bien fonctionné, mais je continue à lutter avec où mettre la logique métier à base d'objets de base et des champs calculés.

couches actuelles:

  • Présentation
  • Service
  • Dépôt
  • Données / entité

Notre couche référentiel a la plupart de la logique Fetch / valider / enregistrer de base, bien que la couche de service fait beaucoup de la validation plus complexe et d'économie (depuis les opérations de sauvegarde ne vous connecter également, la vérification des autorisations, etc.). Le problème est de savoir où placer le code comme ceci:

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

ou

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

Toutes les pensées?

Était-ce utile?

La solution

Revenons à l'essentiel:

Services

Services sont disponibles en 3 saveurs: Services de domaine , Services d'application et Services d'infrastructure

  • les services de domaine : Encapsule logique métier qui ne fonctionne pas naturellement adapter à l'intérieur d'un objet de domaine. Dans votre cas, tous de votre logique métier.
  • Services d'application : Utilisé par les consommateurs externes de parler à votre système
  • Services d'infrastructure : Permet aux préoccupations techniques abstraites (par exemple MSMQ, fournisseur de messagerie, etc.)

Dépôt

est où votre accès aux données et contrôles cohérence go. En DDD pur, vos Racines globales serait responsable de la vérification de la cohérence (avant la persistance des objets). Dans votre cas, vous devez utiliser les chèques de votre Services de domaine couche.


Solution proposée: fendit vos services existants

Utilisez un nouveau Services de domaine couche pour encapsuler toute logique pour vos DTO et votre cohérence vérifie aussi (en utilisant Caractéristiques , peut-être?).

Utilisez le Application Service pour exposer les méthodes nécessaires chercher (FetchOpenOrdersWithLines), qui transmet ensuite les demandes à votre Référentiel (et l'utilisation des médicaments génériques, comme Jeremy a suggéré). Vous pouvez également envisager d'utiliser Caractéristiques de la requête pour envelopper vos requêtes.

De Référentiel , utilisez Caractéristiques Services de domaine couche pour vérifier la cohérence des objets etc avant persistant vos objets.

Vous pouvez trouver des informations de soutien dans le livre d'Evans:

  • "Services et la couche de domaine isolé" (p 106)
  • "Caractéristiques" (p 224)
  • "Caractéristiques de la requête" (p 229)

Autres conseils

Je suis tenté de répondre Mu , mais je voudrais préciser . En résumé: Ne laissez pas votre choix de ORM dicter la façon dont vous définissez votre modèle de domaine

.

Le but du modèle de domaine est d'être une riche API orientée objet que les modèles du domaine. Pour suivre vrai Domain-Driven Design, le modèle de domaine doit être défini sans contrainte par la technologie .

En d'autres termes, le Domaine modèle est livré premier , et toutes les implémentations spécifiques à la technologie sont ensuite traitées par cartographes carte entre le modèle de domaine et la technologie en question. Cela comprendra souvent les deux sens:. À la couche d'accès aux données où le choix des ORM peut introduire des contraintes et à la couche d'interface utilisateur où la technologie de l'interface utilisateur impose des exigences supplémentaires

Si la mise en œuvre est extraordinairement loin du modèle de domaine, nous parlons d'un Couche anti-corruption .

Dans votre cas, ce que vous appelez un modèle de domaine anémiques est en fait la couche d'accès aux données. Votre meilleur recours serait de définir Référentiels que l'accès au modèle à vos entités d'une manière technologiquement neutre.

À titre d'exemple, regardons votre ordre entité. Modélisation d'un ordre non contraint par la technologie pourrait nous conduire à quelque chose comme ceci:

public class Order
{
    // constructors and properties

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

Notez que ce un objet Plain Old CLR ( POCO ) et est donc sans contrainte par la technologie. Maintenant, la question est de savoir comment vous obtenez ceci dans et hors de votre magasin de données?

Cela devrait se faire via un résumé IOrderRepository:

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
}

Vous pouvez maintenant mettre en œuvre IOrderRepository en utilisant votre ORM de choix. Cependant, certains ORM (comme Entity Framework de Microsoft) vous oblige à tirer les classes de données de certaines classes de base, donc cela ne correspond pas du tout avec des objets de domaine comme Poços. À cet effet, la cartographie est nécessaire.

La chose importante à réaliser est que vous pouvez avoir fortement les classes de données typé que sémantiquement ressembler à vos entités de domaine. Cependant, ceci est un détail pur de mise en œuvre, afin de ne pas confondre par là. Une classe d'ordre qui dérive de par exemple EntityObject est pas un classe domaine - c'est un détail de mise en œuvre, de sorte que lorsque vous implémentez IOrderRepository, vous devez mapper l'Ordre classe de données à l'ordre Doman classe

.

Cela peut être un travail fastidieux, mais vous pouvez utiliser AutoMapper pour le faire pour vous.

Voici comment une implémentation de la méthode SelectSingle pourrait ressembler à:

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

Ceci est exactement ce que la couche de service est pour - je l'ai vu aussi des applications où il est appelé la couche BusinessLogic.

Ce sont les routines que vous aurez envie de passer la plupart de vos tests de temps, et si elles sont dans leur propre couche puis se moquant la couche de dépôt devrait être simple.

La couche de dépôt doit être genericized autant que possible, il est donc pas un endroit approprié pour la logique métier qui est individuel à des classes particulières.

D'après ce que vous dites peut-être que vous pensez de façon trop rigide au sujet de votre service des couches et du référentiel. On dirait que vous ne voulez pas que votre couche de présentation d'avoir une dépendance directe sur la couche dépôt et pour y parvenir vous dupliquez les méthodes de vos Référentiels (vos méthodes passe-through) dans la couche de service.

Je doute que. Vous pourrez vous détendre et qui permettent à la fois à utiliser dans votre couche de présentation et vous rendre la vie plus simple pour commencer. Peut-être demander à votre auto ce que votre réalisation en se cachant les Référentiels comme ça. Vous êtes déjà abstraire la persistance et l'interrogation de mise en œuvre avec eux. C'est grand et ce qu'ils sont conçus pour. Il semble que vous essayez de créer une couche de service qui cache le fait vos entités sont conservées à tous. Je demande pourquoi?

En ce qui concerne le calcul des totaux de commande, etc. Votre couche de service serait la maison naturelle. Une classe SalesOrderCalculator avec des méthodes LineTotal (LineItem LineItem) et OrderTotal (ordre de la commande) serait bien. Vous pouvez également envisager la création d'une usine appropriée par exemple OrderServices.CreateOrderCalculator () pour changer la mise en œuvre si nécessaire (taxe sur les rabais de commande a des règles spécifiques pays par exemple). Cela pourrait aussi constituer un point d'entrée unique pour commander des services et faire trouver des choses faciles grâce à IntelliSense.

Si tout cela semble irréalisable, il peut être vous devez penser plus profondément à ce que vos abstractions réalisent, comment ils se rapportent les uns aux autres et responsabilité unique principe . Un référentiel est une abstraction de l'infrastructure (cachant comment les entités sont enregistrées et récupérées). Services abstraire la mise en œuvre des actions commerciales ou des règles et permettent une meilleure structure ou versioning variance. Ils ne sont généralement pas en couches de la façon que vous décrivez. Si vous avez des règles de sécurité complexes dans vos services, vos Référentiels peut être le meilleur accueil. Dans un modèle de style DDD typique, Référentiels, entités, objets de valeur et des services seraient tous utilisés le long du côté de l'autre dans la même couche et dans le cadre du même modèle. Les couches supérieures (généralement la présentation) seraient donc isolées par ces abstractions. Dans le cadre du modèle de la mise en œuvre d'un service peut utiliser l'abstraction d'un autre. Un autre raffinement ajoute des règles à qui contient des références à laquelle des entités ou des objets de valeur qui instituent une contexte du cycle de vie plus formel. Pour plus d'informations sur ce que je vous recommande d'étudier le livre Eric Evans ou domaine Driven Concevez rapidement .

Si votre technologie ORM ne gère que des objets DTO bien, cela ne signifie pas que vous devez jeter des objets d'entités riches. Vous pouvez toujours emballer vos objets DTO avec des objets d'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);
   }

}

J'ai trouvé le nouveau livre de Dino Esposito Microsoft® .NET: architecturer Applications pour la Enterprise pour être un grand dépôt de connaissances pour ces types de questions et de problèmes.

La couche de service.

Si vous voulez ajouter un peu de comportement à vos entités, mais ne pouvez pas modifier vos entités, donner des méthodes d'extension d'essayer. Je ne le fais pour des scénarios simples comme dans votre exemple si. Quelque chose de plus complexe ou que les coordonnées entre plusieurs entités et / ou des services, des couches, ou tout ce qui devrait être dans un service de domaine tel que suggéré déjà.

Par exemple (de vos exemples):

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();
    ...
  }
}

S'il y a très peu de ces ajouts que vous voulez, vous pouvez simplement les mettre tous dans un « DomainExtensions » classe, mais je autrement suggère de les traiter avec respect régulier et de garder toutes les extensions d'une entité dans une classe son propre fichier.

Pour votre information: La seule fois que je l'ai fait c'est quand j'avais une solution L2S et ne voulait pas salir avec les partials. Je ne ai pas eu aussi de nombreuses extensions parce que la solution était petite. J'aime l'idée d'aller avec un service de domaine complet soufflé couche mieux.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top