Je décide si je devrais utiliser un modèle de domaine riche par rapport à un modèle de domaine anémique et je recherche de bons exemples des deux.
Je construisais des applications Web en utilisant un modèle de domaine Anemic, soutenu par un système de couche Service -> Référentiel -> Stockage, en utilisant FluentValidation pour la validation de BL et en plaçant tous mes BL dans la couche Service.
J'ai lu le livre DDD d'Eric Evan et celui-ci (avec Fowler et d'autres) semble penser que les modèles de domaine anémique sont un anti-modèle.
Je voulais donc vraiment avoir un aperçu de ce problème.
De plus, je suis vraiment à la recherche de bons exemples (de base) d’un modèle de domaine riche et des avantages qu’il offre par rapport au modèle de domaine anémique.
Bozhidar Bozhanov semble plaider en faveur du modèle anémique dans this blog.
Voici le résumé qu'il présente:
les objets de domaine ne doivent pas être gérés par Spring (IoC), ni DAO, ni aucun élément lié à l'infrastructure ne doivent y être injectés
les objets de domaine ont les objets de domaine dont ils dépendent définis par hibernate (ou le mécanisme de persistance)
les objets de domaine exécutent la logique métier, comme le fait DDD, mais cela n'inclut pas les requêtes de base de données ni les opérations CRUD uniquement sur l'état interne de l'objet.
il y a rarement besoin de DTO - les objets de domaine sont les DTO eux-mêmes dans la plupart des cas (ce qui enregistre du code standard)
les services effectuent des opérations CRUD, envoient des courriers électroniques, coordonnent les objets de domaine, génèrent des rapports basés sur plusieurs objets de domaine, exécutent des requêtes, etc.
la couche service (application) n’est pas aussi fine, mais n’inclut pas les règles d’entreprise intrinsèques aux objets du domaine
la génération de code doit être évitée. L'abstraction, les modèles de conception et l'ID doivent être utilisés pour surmonter le besoin de génération de code et, finalement, pour éliminer la duplication de code.
METTRE À JOUR
J'ai récemment lu this article dans lequel l'auteur préconise de suivre une sorte d'approche hybride - les objets de domaine peuvent répondre à diverses questions basées uniquement sur leur état (ce qui dans le cas de modèles totalement anémiques serait probablement effectué dans la couche service)
La différence est qu'un modèle anémique sépare la logique des données. La logique est souvent placée dans les classes nommées **Service
, **Util
, **Manager
, **Helper
et ainsi de suite. Ces classes implémentent la logique d'interprétation des données et prennent donc le modèle de données comme argument. Par exemple.
public BigDecimal calculateTotal(Order order){
...
}
alors que l'approche du domaine riche l'inverse en plaçant la logique d'interprétation des données dans le modèle du domaine riche. Ainsi, il rassemble la logique et les données et un modèle de domaine riche ressemblerait à ceci:
order.getTotal();
Cela a un impact important sur la cohérence des objets. Étant donné que la logique d'interprétation des données englobe les données (les données ne sont accessibles que par des méthodes objet), les méthodes peuvent réagir aux changements d'état d'autres données -> C'est ce que nous appelons le comportement.
Dans un modèle anémique, les modèles de données ne peuvent pas garantir qu'ils sont dans un état légal alors qu'ils le peuvent dans un modèle à domaine riche. Un modèle de domaine riche applique les principes OO tels que l'encapsulation, la dissimulation d'informations et la synthèse des données et de la logique. Par conséquent, un modèle anémique est un anti-modèle du point de vue de OO.
Pour un aperçu plus approfondi, consultez mon blog https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/
Mon point de vue est le suivant:
Modèle de domaine anémique = tables de base de données mappées sur des objets (valeurs de champ uniquement, pas de comportement réel)
Modèle de domaine riche = une collection d'objets exposant le comportement
Si vous souhaitez créer une application CRUD simple, un modèle anémique avec un framework MVC classique est peut-être suffisant. Mais si vous voulez implémenter une sorte de logique, un modèle anémique signifie que vous ne ferez pas de programmation orientée objet.
* Notez que le comportement des objets n'a rien à voir avec la persistance. Une couche différente (Data Mappers, Repositories e.t.c.) est responsable de la persistance des objets de domaine.
Tout d’abord, j’ai copié-collé la réponse de cet article http://msdn.Microsoft.com/en-gb/magazine/dn385704.aspx
La figure 1 montre un modèle de domaine anémique, qui est essentiellement un schéma avec des accesseurs et des setters.
Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables
public class Customer : Person
{
public Customer()
{
Orders = new List<Order>();
}
public ICollection<Order> Orders { get; set; }
public string SalesPersonId { get; set; }
public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; }
public string EmailAddress { get; set; }
public string Phone { get; set; }
}
Dans ce modèle plus riche, plutôt que d'exposer simplement les propriétés à lire et à écrire, La surface publique du Client est constituée de méthodes explicites.
Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties
public class Customer : Contact
{
public Customer(string firstName, string lastName, string email)
{
FullName = new FullName(firstName, lastName);
EmailAddress = email;
Status = CustomerStatus.Silver;
}
internal Customer()
{
}
public void UseBillingAddressForShippingAddress()
{
ShippingAddress = new Address(
BillingAddress.Street1, BillingAddress.Street2,
BillingAddress.City, BillingAddress.Region,
BillingAddress.Country, BillingAddress.PostalCode);
}
public void CreateNewShippingAddress(string street1, string street2,
string city, string region, string country, string postalCode)
{
ShippingAddress = new Address(
street1,street2,
city,region,
country,postalCode)
}
public void CreateBillingInformation(string street1,string street2,
string city,string region,string country, string postalCode,
string creditcardNumber, string bankName)
{
BillingAddress = new Address (street1,street2, city,region,country,postalCode );
CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
}
public void SetCustomerContactDetails
(string email, string phone, string companyName)
{
EmailAddress = email;
Phone = phone;
CompanyName = companyName;
}
public string SalesPersonId { get; private set; }
public CustomerStatus Status { get; private set; }
public Address ShippingAddress { get; private set; }
public Address BillingAddress { get; private set; }
public CustomerCreditCard CreditCard { get; private set; }
}
L'un des avantages des classes de domaine riches est que vous pouvez appeler leur comportement (méthodes) chaque fois que vous avez la référence à l'objet dans n'importe quel calque. En outre, vous avez tendance à écrire des méthodes petites et distribuées qui collaborent ensemble. Dans les classes de domaine anémique, vous avez tendance à écrire de grosses méthodes procédurales (dans la couche de service) qui sont généralement pilotées par le cas d'utilisation. Ils sont généralement moins faciles à gérer que les classes de domaine riches.
Un exemple de classes de domaine avec des comportements:
class Order {
String number
List<OrderItem> items
ItemList bonus
Delivery delivery
void addItem(Item item) { // add bonus if necessary }
ItemList needToDeliver() { // items + bonus }
void deliver() {
delivery = new Delivery()
delivery.items = needToDeliver()
}
}
La méthode needToDeliver()
renverra la liste des articles à livrer, y compris les bonus. Il peut être appelé à l'intérieur de la classe, à partir d'une autre classe associée ou d'une autre couche. Par exemple, si vous passez Order
à afficher, vous pouvez utiliser needToDeliver()
de Order
sélectionné pour afficher la liste des éléments à confirmer par l'utilisateur avant qu'il ne clique sur le bouton Enregistrer pour conserver la Order
.
Répondre au commentaire
Voici comment j'utilise la classe de domaine du contrôleur:
def save = {
Order order = new Order()
order.addItem(new Item())
order.addItem(new Item())
repository.create(order)
}
La création de Order
et de sa LineItem
fait l'objet d'une transaction. Si l'une des LineItem
ne peut pas être créée, aucune Order
ne sera créée.
J'ai tendance à avoir une méthode qui représente une transaction unique, telle que:
def deliver = {
Order order = repository.findOrderByNumber('ORDER-1')
order.deliver()
// save order if necessary
}
Tout ce qui se trouve dans deliver()
sera exécuté en une seule transaction. Si j'ai besoin d'exécuter de nombreuses méthodes non liées dans une seule transaction, je créerais une classe de service.
Pour éviter les exceptions de chargement paresseux, j'utilise le graphe d'entités nommées JPA 2.1. Par exemple, dans l'écran Controller for Delivery, je peux créer une méthode pour charger l'attribut delivery
et ignorer bonus
, telle que repository.findOrderByNumberFetchDelivery()
. Dans l'écran des bonus, j'appelle une autre méthode qui charge l'attribut bonus
et ignore delivery
, telle que repository.findOrderByNumberFetchBonus()
. Cela nécessite une dicipline car je ne peux toujours pas appeler deliver()
à l'intérieur de l'écran des bonus.
Quand j’écrivais des applications de bureau monolithiques, je construisais des modèles de domaine riches, j’appréciais de les construire.
Maintenant, j'écris des microservices HTTP minuscules, il y a le moins de code possible, y compris des DTO anémiques.
Je pense que DDD et cet argument anémique datent de l'ère des applications de bureau ou de serveur monolithiques. Je me souviens de cette époque et je conviendrais que les modèles anémiques sont étranges. J'ai construit une grosse application de trading FX monolithique et il n'y avait pas de modèle, vraiment, c'était horrible.
Avec microservices, les petits services avec leur comportement riche sont sans doute les modèles et les agrégats composables dans un domaine. Ainsi, les implémentations de microservice elles-mêmes peuvent ne pas nécessiter davantage de DDD. L'application de microservice peut être le domaine.
Un microservice orders peut avoir très peu de fonctions, exprimées en ressources RESTful ou via SOAP ou autre. Le code de microservice des commandes peut être extrêmement simple.
Un service unique (micro) plus volumineux, plus monolithique, notamment celui qui le conserve en mémoire vive, pourrait tirer parti de la DDD.
Les modèles de domaine anémique sont importants pour l'ORM et la facilité de transfert sur les réseaux (élément vital de toutes les applications commerciales), mais OO est très important pour l'encapsulation et la simplification des parties "transactionnelles/de gestion" de votre code.
Par conséquent, l’important est d’être capable d’identifier et de convertir d’un monde à l’autre.
Name Anemic modélise quelque chose comme AnemicUser, ou UserDAO, etc. afin que les développeurs sachent qu'il existe une meilleure classe à utiliser. Ils disposent ensuite d'un constructeur approprié pour la classe none.
User(AnemicUser au)
et méthode adaptateur pour créer la classe anemic pour le transport/la persistance
User::ToAnemicUser()
Viser à utiliser l'utilisateur non anémique partout en dehors du transport/persistance
Voici un exemple qui pourrait aider:
Anémique
class Box
{
public int Height { get; set; }
public int Width { get; set; }
}
Non anémique
class Box
{
public int Height { get; private set; }
public int Width { get; private set; }
public Box(int height, int width)
{
if (height <= 0) {
throw new ArgumentOutOfRangeException(nameof(height));
}
if (width <= 0) {
throw new ArgumentOutOfRangeException(nameof(width));
}
Height = height;
Width = width;
}
public int area()
{
return Height * Width;
}
}