J'essaie de comprendre DDD mais je suis coincé. Voici comment j'installe mon projet:
Data Access Layer
-Entity models that map to the db
-Db connection stuff
-Repositories implementations
Domain Layer
-Models that represent the DAL entity models
-Repositories interfaces
Application Layer
-MVC application that uses Domain models
Le premier problème que je vois ici est que les modèles de domaine sont exactement les mêmes que les modèles d'entité, et j'ai un sérieux problème avec cela: les modèles d'entité ont évidemment une validation configurée en eux, des choses comme "longueur maximale", "nullable", "requis", etc. Maintenant, pour me conformer à ce que je comprends, c'est DDD, je ne peux pas utiliser directement ces modèles n'importe où, sauf le DAL, alors j'ai créé ma couche de domaine. Dans la couche domaine, j'ai toutes ces règles de validation dupliquées pour la validation de l'interface utilisateur, et ce qui est encore pire, c'est que si je dois changer une règle, je devrai la changer à deux endroits: le DAL et le domaine.
Exemple:
User Entity in DAL
Name (required)
Last name (required)
Email (required, maxlen 120)
Username (required, maxlen 120)
User Domain Model
Name (required)
Last name (required)
Email (required, maxlen 120)
Username (required, maxlen 120)
Une autre chose que je trouve très bizarre, c'est que l'organisation des référentiels dans cette architecture. Suite à ce que j'ai lu, j'ai créé une interface GenericRepository et une interface UserRepository, qui hérite de GenericRepository, le tout dans la couche Domain. J'ai implémenté GenericRepository dans le DAL, et l'implémentation crée un DAO pour le type d'entité utilisé pour créer le référentiel. Jusqu'ici tout va bien.
Ensuite, j'ai procédé à l'implémentation de UserRepository, et ici j'ai un autre problème: l'interface UserRepository attend le modèle d'utilisateur de domaine, et lorsque j'essaie d'implémenter l'interface dans le DAL, je dois l'implémenter en utilisant le modèle d'utilisateur de domaine, ce qui provoque le DAO à créer pour un modèle de domaine, pas un modèle DAL, et cela n'a aucun sens. La seule solution serait de référencer le DAL dans la couche Domaine, ce qui est faux.
Domain Layer:
public interface IGenericRepository<TEntity>
{
TEntity FindById(TKey id);
}
public interface IUserRepository : IGenericRepository<Domain.User>
{
Task<User> FindByUserNameAsync(string userName);
}
DAL:
public abstract class GenericRepository<TEntity> : IGenericRepository<TEntity>
{
protected DbContext ctx;
protected DAO<Entity> dao;
public GenericRepository(DbContext context)
{
ctx = context;
dao= ctx.Dao<TEntity>();
}
public virtual TEntity FindById(TKey id)
{
return dao.Find(id);
}
}
public class UserRepository : GenericRepository<Domain.Models.User>, IUserRepository
{
public UserRepository(DbContext context)
: base(context)
{
// THIS WILL CREATE A DAO FOR A DOMAIN MODEL
}
// rest of code...
}
Quelqu'un peut-il faire la lumière sur ce qui me manque dans DDD?
D'accord. Il y a beaucoup à déballer ici, mais je pense que la cause première de votre confusion provient du démarrage de ce processus avec le modèle physique au premier plan. Cela provoque généralement toutes sortes de problèmes pour les personnes qui essaient d'abord d'implémenter DDD. Le but de DDD est de modéliser le comportement d'un système de telle sorte que le résultat soit une abstraction utile des exigences de fonction du domaine central. Pour l'instant, oubliez simplement votre DAL. C'est un détail d'implémentation.
Commencez par modéliser votre système en organisant les modèles par comportement dans des contextes délimités. C'est le comportement des modèles qui les relie vraiment. Les données/attributs que contiennent les entités sont rarement un bon point de départ pour modéliser les exigences fonctionnelles d'un système complexe. Je serais ravi de fournir quelques exemples, mais vous n'avez pas spécifié votre domaine, je vais donc vous donner quelques conseils:
En commençant par votre modèle de domaine User
. Je parie que l'évier de la cuisine est dû au fait que vous avez une entité User
. Le problème avec cela est que User
implique peu ou pas de comportement (utilise quoi?), Englobe probablement trop de connaissances et est donc trop abstrait. Que font vos utilisateurs (hors authentification)? Boutique? Shopper
. Commentaire? Commenter
/Poster
. Vendre? Seller
. La partie importante est que ces ne sont pas mutuellement exclusifs! Un User
peut être tout cela en fonction du comportement dans lequel ils vont s'engager. Le fait qu'ils mappent tous à la même table de base de données est un détail d'implémentation.
Tu vois où je vais ici? Vous pouvez avoir un contexte Shopping qui a Shopper
, ShoppingCart
et CartItem
et un contexte de facturation qui a Buyer
, PurchaseRequest
et LineItem
où les modèles de chacun correspondent respectivement à un [User]
, [Order]
, et [OrderItem]
table de base de données. Votre modèle doit être au centre absolu de ce processus. Pas comment cela persiste.
Quant à une réponse directe à votre question. Il existe un certain nombre d'objets communs trouvés dans un domaine. DomainModels
et ValueObjects
sont les pierres angulaires de votre modèle, mais vous trouverez souvent Repositories
et Factories
jouant un rôle de support. Étant donné que les référentiels et les usines ont presque toujours besoin de connaître les détails d'implémentation de votre domaine, ils font généralement partie de votre modèle de domaine (mais pas du diagramme, par exemple).
Il semble que vous ayez vu quelqu'un implémenter une conception DDD en utilisant l'architecture que vous mentionnez dans la question, mais ce n'est pas ce qu'est DDD. Ce n'est là qu'une des nombreuses implémentations possibles. Je vous recommande de suivre la réponse de la diapositive côté roi concernant DDD.
En ce qui concerne votre question, je la vois plutôt comme une question générale sur le cadre d'entité et l'organisation des projets. Et voici quelques commentaires:
Vos modèles de domaine ne représentent pas vos modèles DAL. C'est l'inverse. Vous devez concevoir vos modèles de domaine pour représenter certains concepts de votre domaine. Votre DAL n'est alors qu'un utilitaire pour conserver ces modèles. Personnellement, je n'utilise pas du tout d'entités DAL. EF peut mapper vos modèles de domaine directement. Utilisez des fichiers de mappage avec EntityTypeConfiguration au lieu d'attributs, qui contaminent votre modèle ou votre API courante, ce qui est très bavard et désordonné.
Ne créez pas d'interface de référentiel générique. Cela conduira à avoir des référentiels avec des méthodes qui ne devraient pas être là. Chaque référentiel ne doit avoir que les méthodes nécessaires selon vos besoins.
Définissez les interfaces de référentiel dans votre couche métier. C'est votre couche métier qui dicte ce qu'un référentiel doit implémenter, et non l'inverse. Ensuite, vous pouvez avoir un ou plusieurs projets implémentant ces référentiels, en référençant le (s) projet (s) de la couche métier, afin qu'ils puissent mapper le modèle et implémenter les interfaces.
Ne consommez pas votre modèle de domaine à partir de votre couche d'interface utilisateur. Votre couche métier doit fournir des interfaces avec des opérations d'écriture (commandes), qui acceptent les DTO d'entrée et avec des opérations de lecture (requêtes), qui renvoie les DTO.
Définissez ces interfaces et DTO dans un projet distinct appartenant à votre couche métier, puis votre couche d'interface utilisateur ou les contrôleurs API Web peuvent référencer ce projet et consommer les interfaces.
Si votre couche métier doit accéder à des services externes, définissez des interfaces pour eux dans la couche métier, avec les méthodes exactes dont vous avez besoin à partir de ces services. Ensuite, vous pouvez créer des projets implémentant ces interfaces. C'est exactement le même concept qu'avec les référentiels.
(avertissement, je ne force pas ou ne propose pas cela comme une réponse correcte unique à votre question, c'est simplement comment je le fais dans mes solutions, basé sur le architecture Onion .)
Je crois que votre principale confusion vient de la duplication d'objets ou de modèles de domaine dans la couche DAL. Je pense toujours à l'objet Domain comme une entité dans ma base de données backend[*1]. Pour moi, c'est le principal élément d'information dont j'ai besoin pour que la solution fonctionne. Il n'est donc pas nécessaire de répliquer la couche DAL.
Voici comment je structure mon dossier de solution (en fonction de votre saisie):
Couche de domaine
Des modèles qui représentent Modèles d'entités DAL objets de base de données (tables et vues).
Interfaces de référentiels .
Couche d'accès aux données
Trucs de connexion DB.
Implémentations de référentiels .
Configuration des modèles (configuration Fluent).
Couche infrastructure (entreprise)
Services externes.
Utilitaires.
Services spécifiques utilisés plus tard dans la couche front-end.
UI Application Couche
Projet WebAPI, parfois dans son propre dossier pour une séparation plus claire[* 2].
MVC/angular/react/etc application qui utilise Modèles de domaine toutes les couches ci-dessus selon l'architecture Oignon.
Chaque couche, de haut en bas, fait référence à tout des couches précédentes. Ainsi, au niveau 3. (Infrastructure), je fais référence aux projets de couche Domaine et DAL, etc.
[*1] J'inclus également généralement toutes les vues de base de données parmi les objets de domaine, ce qui élimine largement le besoin d'un "UserService" juste pour créer des agrégats pour la couche front-end.
[* 2] En fonction de la taille/portée de la solution, j'omet le projet WebAPI et consomme simplement toutes les couches précédentes directement dans l'application MVC. Cependant, si j'utilise WebAPI, l'application MVC utilise uniquement cette WebAPI et, dans de rares cas, référence les services externes de la couche Infrastructure par exemple.
Modifier selon le commentaire:
La couche Infrastructure comprend généralement, mais pas nécessairement, ces trois projets:
Services externes - disons que mon application a besoin des données d'un service DMV public. C'est l'endroit où je voudrais l'implémenter. Ajoutez une référence de service, ajoutez une logique pour appeler le service DMV, préparez des objets DTO de demande et de réponse qui sont ensuite utilisés pour communiquer avec WebAPI ou directement vers l'application MVC.
Utilitaires - c'est un assez gros projet de nombreuses extensions ou méthodes statiques comme par exemple "StringToDateTime" ou "IsValidTaxNumber" etc. Celui-ci nous nous efforçons de garder libre de référencer d'autres projets. Je crois que la seule dépendance dont celle-ci dispose actuellement est Newtonsoft Json utilisé dans la section Sérialisation.
Services spécifiques - ceci est principalement utilisé comme middleware pour empêcher mes contrôleurs (API ou MVC) de ballonner. Si j'ai un client contrôleur et qu'il y a une logique métier, je le mettrai dans ce projet. De cette façon, j'ai une bonne répartition des couches de présentation et de gestion/logique.