Dois-je utiliser le référentiel dans l'objet de domaine ou repousser l'objet de domaine vers la couche de service?

10

Je viens d'un monde de scripts de transaction et je commence tout juste à jeter un œil à DDD. Je ne suis pas sûr de la bonne façon d'intégrer une conception DDD avec la persistance de la base de données. Voici ce que j'ai:

Classe de service appelée OrganisationService dont l'interface contient des méthodes de récupération et de sauvegarde des instances d'objets de domaine Organisation. L'organisation est une racine agrégée et possède d'autres données qui s'y rapportent: membres et licences. La première base de données EF6 DBContext est utilisée au sein de l'OrganisationService pour récupérer les entités OrganisationDB et les entités MemberDB et LicenseDB associées. Tous ces éléments sont transformés en leurs équivalents de classe d'objets de domaine lorsqu'ils sont récupérés par l'OrganisationService et chargés dans l'objet de domaine Organisation. Cet objet ressemble à ceci:

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }
}

Je n'utilise pas le modèle de référentiel dans OrganisationService ... J'utilise EF lui-même comme référentiel car il semble qu'EF6 ait largement rendu le référentiel redondant maintenant.

À ce stade de la conception, l'objet domaine Organisation est anémique: il ressemble à la classe Organisation EF POCO. La classe OrganisationService ressemble beaucoup à un référentiel!

Maintenant, je dois commencer à ajouter de la logique. Cette logique inclut la gestion des licences et des membres d'une organisation. Maintenant, à l'époque des scripts de transaction, j'ajoutais des méthodes dans OrganisationService pour gérer ces opérations et appelais dans un référentiel pour interagir avec la base de données, mais avec DDD, je pense que cette logique devrait être encapsulée dans l'objet de domaine Organisation lui-même ...

C'est là que je ne suis pas sûr de ce que je dois faire: je devrai conserver ces données dans la base de données dans le cadre de la logique. Cela signifie-t-il que je devrais utiliser DbContext dans l'objet de domaine Organisation pour ce faire? L'utilisation du référentiel / EF dans l'objet de domaine est-elle une mauvaise pratique? Si oui, où cette persistance appartient-elle?

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }

    public void AddLicensesToOrganisation(IList<License> licensesToAdd)
    {
        // Do validation/other stuff/

        // And now Save to the DB???
        using(var context = new EFContext())
        {
            context.Licenses.Add(...
        }

        // Add to the Licenses collection in Memory
        Licenses.AddRange(licensesToAdd);
    }
}

Dois-je plutôt muter l'objet de domaine Organisation en mémoire, puis le repousser à OrganisationService pour qu'il persiste? Ensuite, je dois suivre ce qui a réellement changé sur l'objet (c'est ce qu'EF fait à ses propres POCO! Je ressens en quelque sorte que EF n'est pas seulement un remplacement de référentiel, mais pourrait également être la couche de domaine!)

Tout conseil ici est apprécié.

MrLane
la source

Réponses:

2

Vous pouvez adopter l'une ou l'autre approche et la faire fonctionner correctement - il y a, bien sûr, des avantages et des inconvénients.

Entity Framework est définitivement destiné à imprégner vos entités de domaine. Cela fonctionne bien lorsque vos entités de domaine et vos entités de données sont les mêmes classes. C'est beaucoup plus agréable si vous pouvez compter sur EF pour suivre les changements pour vous, et appeler simplement context.SaveChanges()lorsque vous avez terminé votre travail transactionnel. Cela signifie également que vos attributs de validation ne doivent pas être définis deux fois, une fois sur vos modèles de domaine et une fois sur vos entités persistantes - des choses comme [Required]ou [StringLength(x)]peuvent être vérifiées dans votre logique métier , vous permettant d'attraper des états de données non valides avant d'essayer de faire une transaction DB et obtenir unEntityValidationException. Enfin, il est rapide de coder - vous n'avez pas besoin d'écrire une couche de mappage ou un référentiel, mais pouvez plutôt travailler directement avec le contexte EF. C'est déjà un référentiel et une unité de travail, donc des couches supplémentaires d'abstraction n'accomplissent rien.

Un inconvénient de la combinaison de votre domaine et des entités persistantes est que vous vous retrouvez avec un tas d' [NotMapped]attributs dispersés dans vos propriétés. Souvent, vous souhaiterez des propriétés spécifiques au domaine qui sont soit des filtres de lecture seule sur les données persistantes, soit définies plus tard dans votre logique métier et non persistées dans votre base de données. Parfois, vous souhaiterez exprimer vos données dans vos modèles de domaine d'une manière qui ne fonctionne pas très bien avec une base de données - par exemple, lorsque vous utilisez des énumérations, Entity les mappera sur une intcolonne - mais peut-être voulez-vous mapper les rendre lisibles par l'homme string, vous n'avez donc pas besoin de consulter une recherche lors de l'examen de la base de données. Ensuite, vous vous retrouvez avec une stringpropriété qui est mappée, unenumpropriété qui n'est pas (mais obtient la chaîne et les mappes à l'énumération), et une API qui expose les deux! De même, si vous souhaitez combiner des types complexes (tables) dans différents contextes, vous pouvez vous retrouver avec un mappedOtherTableId et une ComplexTypepropriété non mappée , qui sont tous deux exposés en tant que public sur votre classe. Cela peut être déroutant pour quelqu'un qui ne connaît pas le projet et alourdit inutilement vos modèles de domaine.

Plus ma logique / domaine métier est complexe, plus je trouve contraignant ou encombrant la combinaison de mon domaine et d'entités persistantes. Pour les projets avec des délais courts ou qui n'expriment pas une couche métier complexe, je pense que l'utilisation d'entités EF à ces deux fins est appropriée, et il n'est pas nécessaire d'abstraire votre domaine de votre persistance. Pour les projets qui nécessitent une facilité d'extension maximale ou qui doivent exprimer une logique très compliquée, je pense que vous feriez mieux de séparer les deux et de gérer la complexité de persistance supplémentaire.

Une astuce pour éviter le problème du suivi manuel des modifications de votre entité consiste à stocker l'ID d'entité persistant correspondant dans votre modèle de domaine. Cela peut être rempli automatiquement par votre couche de cartographie. Ensuite, lorsque vous devez conserver une modification sur EF, récupérez l'entité persistante appropriée avant d' effectuer tout mappage. Ensuite, lorsque vous mapperez les modifications, EF les détectera automatiquement et vous pourrez appeler context.SaveChanges()sans avoir à les suivre à la main.

public class OrganisationService
{
    public void PersistLicenses(IList<DomainLicenses> licenses) 
    {
        using (var context = new EFContext()) 
        {
            foreach (DomainLicense license in licenses) 
            {
                var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
                MappingService.Map(persistedLicense, license); //Right-left mapping
                context.Update(persistedLicense);
            }
            context.SaveChanges();
        }
    }
}
NWard
la source
Existe-t-il un problème lors de la combinaison de différentes approches avec une complexité différente des données? si vous avez quelque chose qui correspond bien à DB, utilisez directement EF. Si vous avez quelque chose qui n'en a pas, introduisez un mappage / abstraction avant d'utiliser EF.
Euphoric
@Euphoric les différences de domaine et de base de données peuvent être gérées dans le mappage. Je ne peux pas penser à un cas d'utilisation où ajouter le domaine deux fois (non persistant et persistant) et gérer le suivi des modifications à la main est moins de travail que l'ajout de mappages pour des cas spéciaux. Pouvez-vous m'en indiquer un? (Je suppose que l'utilisation de NHibernate, peut-être que EF est plus limité?)
Firo
Réponse très utile, pratique et informative. Marquage comme réponse. Merci!
MrLane
3

Je ne connais pas EF6 mais d'autres ORM gèrent cela de manière transparente, vous n'aurez donc pas à ajouter explicitement les licences au contexte, il les détectera lors de l'enregistrement des modifications. Donc, la méthode se résumera à

public void AddLicenses(IEnumerable<License> licensesToAdd)
{
    // Do validation/other stuff/
    // Add to the Licenses collection in Memory
    Licenses.AddRange(licensesToAdd);
}

et le code qui l'entoure

var someOrg = Load(orgId);

someOrg.AddLicenses(someLicences);

SaveChanges();
Firo
la source
Mes objets de domaine ne sont pas les entités, donc les ajouter à la collection de licences, qui n'est qu'une liste, ne le coupera pas. La question ne concerne pas tellement la FE, mais c'est l'endroit où la persistance réelle a lieu. Toute persistance dans EF doit avoir lieu dans une étendue de contexte. La question est de savoir où cela appartient-il?
MrLane
2
les entités de domaine et les entités persistantes peuvent / devraient être les mêmes sinon vous introduisez une autre couche d'abstraction qui 99% du temps n'ajoute aucune valeur et le suivi des modifications est perdu.
Firo