Cet article a fait l'objet d'une traduction automatique.
Modèles de domaine
Utilisation du modèle de modèle de domaine
Udi Dahan
Cet article explique :
|
Cet article utilise les technologies suivantes : Modèle de modèle de domaine |
Dans cet article, nous passent par l'emploient raisons à (et non vers) le modèle de modèle de domaine, les avantages qu'il apporte, ainsi que fournir des conseils pratiques sur garder la solution globale aussi simple que possible.
Contenu
Qu'est-ce qu'il ?
Raisons de pas utiliser le modèle de domaine
Technologie
Raisons pour utiliser le modèle de domaine
Scénarios d'utilisation pas le modèle de domaine
Scénarios d'utilisation du modèle de domaine
Interactions plus complexes
Technologie récente des Business Rules
Événements de domaine et leurs appelants
Événements de domaine explicite
Aptitude au test
Commandes et requêtes
Conserver l'entreprise dans le domaine
Accès concurrentiel
Recherche une solution complète
Works cités
Si vous avez fourni à moi il y a quelques années et un message me demande si j'ai déjà utilisé le modèle de modèle de domaine, vous devez ont répondu avec absolu «Oui». J'ai de mon comprendre le modèle. J'avais prise en charge les technologies qui fait fonctionner.
Mais j'aurait été complètement incorrect.
Ma présentation a évolué au fil des années, et vous avez obtenu une réévaluation pour comment une application peut tirer profit de lui-même alignement avec les mêmes principes pilotée par domaine.
Dans cet article, nous allons passer par le biais de raison pour laquelle nous voulons même envisager d'utiliser le modèle de modèle de domaine (ainsi que pourquoi pas), les avantages qu'il est supposé pour mettre, ses interactions avec d'autres parties d'une application et les fonctionnalités nous pouvez souhaitez fournies par les technologies de prise en charge et discuter des conseils pratiques sur garder la solution globale aussi simple que possible.
Qu'est-ce qu'il ?
L'auteur du modèle de modèle de domaine, Martin Fowler, offre cette définition (Fowler, 2003) :
Un modèle d'objet du domaine qui incorpore le comportement et les données.
Pour vous dire la vérité, cette définition peut être interprétée en fonction de presque n'importe quelle partie du code — une assez bonne raison pourquoi je pensais que j'utilisais le modèle lorsqu'en fait je n'était pas true à son intention d'origine.
Nous allons Explorer plus profond.
Raisons de pas utiliser le modèle de domaine
Dans le texte suivant la description d'origine, j'avait initialement remplacé au-delà de ce passage inoffensif, mais il s'avère que de nombreuses décisions importantes dépendre de comprendre qu'il.
Étant donné que le comportement de l'entreprise est soumis à un lot de modification, il est important pouvoir modifier, générer et tester facilement cette couche. Par conséquent, vous souhaiterez le minimum de couplage à partir du modèle de domaine aux autres couches dans le système.
Pour une raison ne pas d'effectuer le modèle de modèle de domaine est utilisé si l'entreprise qu'est l'automatisation de votre logiciel ne modifie pas beaucoup. C'est ne pas de dire qu'elle ne change pas du tout, mais plutôt que les règles sous-jacentes dictée comment est réalisé entreprise ne sont pas très dynamiques. Bien que d'autres facteurs technologiques et environnementales peuvent changer, qui n'est pas le contexte de ce modèle.
Cela exemples de prise en charge plusieurs bases de données (telles que SQL Server et Oracle) ou de plusieurs technologies de l'interface utilisateur (Windows, Web, Mobile et ainsi de suite). Si le comportement de l'entreprise n'a pas changé, ceux-ci ne justifient pas l'utilisation du modèle de modèle de domaine. Qui ne signifie ne pas une pourrait pas obtenir une grande traitent de valeur de l'utilisation technologies prenant en charge le modèle, mais nous devront être honnêtes sur les règles de nous arrêter et pourquoi.
Raisons pour utiliser le modèle de domaine
Dans ces cas où le comportement de l'entreprise est soumis à un lot de modification, avoir un modèle de domaine diminuera le coût total de ces modifications. Avoir tout le comportement de l'entreprise est susceptible de changer encapsulés dans une seule partie de notre logiciel diminue du temps, vous devrez effectuer une modification, car elle sera tout effectuée dans un seul endroit. En isolant ce code autant que possible, nous réduire la probabilité de modifications dans autres emplacements provoquant son arrêt, ce qui diminue le temps que nécessaire pour stabiliser le système.
Scénarios d'utilisation pas le modèle de domaine
Ceci nous mène au champ. 1 fallacy courante sur l'utilisation de modèles de domaine. Je m'a été coupable de rendre cette hypothèse false pour un nombre d'années et voir maintenant où il a conduit m'astray.
Fallacy : Un modèle d'objet persistant est un modèle de domaine
Tout d'abord, un modèle d'objet persistant n'encapsule pas, par nature, tous les comportements de l'entreprise sont susceptibles de changer. Ensuite, un modèle d'objet persistant peut inclure des fonctionnalités non susceptibles de changer.
La nature de cette fallacy est similaire à indiquant que n'importe quel tournevis est un marteau. Alors que vous pouvez (essayez) marteau dans clous avec un tournevis, vous sera très efficace de cette façon. Un peut dire ne que vous ont été est true pour le modèle marteau.
Mettre ce retour concrètes scénarios que nous savons tous et d'amour, passons en revue l'impératif permanente qu'adresse de messagerie d'un utilisateur doit être unique.
Pendant un certain temps, j'ai pensé que le but de disposer d'un modèle de domaine était qu'exigences ainsi soit il implémentées. Toutefois, lorsque nous considérons les instructions du modèle de domaine consiste à capturer ces comportements d'entreprise sont sujets à modification, nous pouvons voir que cette exigence ne tient pas que mold. Il est probable que cela ne change pas.
Par conséquent, choisir d'implémenter un tel impératif dans la partie du système consiste à encapsuler les parties volatiles de l'entreprise est peu judicieux, peut être difficile à implémenter et qui ne peut pas exécuter correctement. Mettre toutes les adresses de messagerie dans la mémoire probablement obtiendrez vous bloqué par la police de performances. De même le modèle de domaine appeler un service, qui appelle la base de données, l'adresse de messagerie s'il est inutile. Une contrainte unique dans la base de données suffirait.
Ce raisonnement pragmatique est très similaire au cœur du modèle de modèle de domaine et conception pilotée par domaine et que va simplifier les choses même si nous s'attaquent exigences plus complexe qu'unicité simple courrier électronique.
Scénarios d'utilisation du modèle de domaine
Règles entreprise qui indiquent quand certaines actions sont autorisées sont adaptées pour implémentées dans un modèle de domaine.
Par exemple, dans un système de commerce électronique une règle indiquant qu'un client peut avoir plus de 1 000 € dans commandes impayés serait probablement appartiennent dans le modèle de domaine. Notez que cette règle implique plusieurs entités et devra être évaluée dans une variété de cas d'utilisation.
Bien sûr, dans un modèle de domaine donné nous aimeriez s'attendent à voir parmi ces types de règles, y compris les cas où certaines règles substituent d'autres personnes. Dans notre exemple ci-dessus, si l'utilisateur effectue une modification à une commande est le Gestionnaire de compte gestionnaire pour le compte appartient le client, puis la règle précédente ne s'applique pas.
Il peut sembler inutile de temps accédant via les règles qui doivent appliquer dans les cas d'utilisation et rapidement trouver une liste des entités et relations entre eux, eschewing la "big conception en amont" pratiques agiles rail par rapport à. Toutefois, les règles métier et les cas d'utilisation sont les raisons très que nous vous imputez le modèle de modèle de domaine en premier lieu.
Lors de la résolution de ces types de problèmes dans le passé, je ne seraient pas avoir pensé deux fois et serait ont conçu rapidement une classe de client avec une collection d'objets Order. Mais nos règles jusqu'à présent n'indiquent qu'une seule propriété selon le client au lieu de cela, UnpaidOrdersAmount. Nous peut passer par plusieurs règles et ne jamais exécuter en quelque chose qui pointait clairement à une collection de commandes. Dans ce cas, maxim agile "vous n'êtes pas peu besoin» (YAGNI) doit nous empêchent de créer cette collection.
Lorsque vous examinez comment conserver ce graphique d'objets, il peut s'avérer indiquée ajouter de prise en charge des objets et collections sous. Nous devons clairement différencier les détails d'implémentation et des comportements métier principaux sont la responsabilité du modèle de domaine.
Interactions plus complexes
Envisagez la nécessité que lorsqu'un client a effectué plus 000 $ intéressant d'achats avec notre entreprise, ils deviennent un client «favori». Lorsqu'un client devient un client par défaut, le système doit les envoyer un message électronique notifiant des avantages de notre programme client par défaut.
Ce scénario diffère de l'impératif d'adresse de messagerie unique décrit précédemment fait que cette interaction implique nécessairement le modèle de domaine. Une option consiste à implémenter cette logique dans le code qui appelle le modèle de domaine comme suit :
public void SubmitOrder(OrderData data)
{
bool wasPreferredBefore = customer.IsPreferred;
// call the domain model for regular order submit logic
if (customer.IsPreferred && !wasPreferredBefore)
// send email
}
Des dangers que nous qui évite l'exemple de code est de vérifier le montant que constitue lorsqu'un client devient par défaut. Que logique est correctement habilitée au modèle de domaine.
Malheureusement, nous pouvons voir que le code est soumis à devenir surgonflé lorsque plusieurs règles sont ajoutés au système qui doit être évaluée lorsque les commandes sont envoyés. Même si nous était de déplacer ce code dans le modèle de domaine, nous avais laissé toujours les problèmes suivants.
Technologie récente des Business Rules
Il existe peut-être autres cas d'utilisation qui entraînent le client devient par défaut. Nous ne devrez dupliquer cette logique dans plusieurs emplacements (Si c'est dans le modèle de domaine ou non), principalement parce que la refactorisation pour une méthode extraite toujours nécessiterait la capture d'état par défaut d'origine du client.
Nous devons même atteindre jusqu'en tant qu'incluent un type de méthode de (AOP) programmation orientée aspect/interception pour éviter la duplication.
Il semble que nous pouvez repenser mieux notre approche afin que nous avons nous-mêmes sur Occam de rasoir. Examinez à nouveau nos exigences peut nous donner certains direction.
Lorsqu'un client est devenu un [un] le système doit [faire quelque chose].
Nous semble manquer une bonne façon de représenter ce modèle de demande, bien que cela semble comme quelque chose qu'un modèle basé sur événement Impossible de traiter bien. De cette façon, si nous sommes doit faire partie plus dans le «doit faire quelque chose», nous peut facilement implémenter qui comme un gestionnaire d'événements.
Événements de domaine et leurs appelants
Événements de domaine sont la manière dont nous explicitement représentent la première partie de la demande décrite :
Lorsqu'un [un] est devenu un [un]...
Bien que nous pouvons implémenter ces événements sur les entités eux-mêmes, il peut être avantageux pour qu'ils être accessible au niveau de l'ensemble du domaine. Nous allons comparer le comporte de la couche de service dans les deux cas :
public void SubmitOrder(OrderData data)
{
var customer = GetCustomer(data.CustomerId);
var sendEmail = delegate { /* send email */ };
customer.BecamePreferred += sendEmail;
// call the domain model for the rest of the regular order submit logic
customer.BecamePreferred -= sendEmail; // to avoid leaking memory
}
S'il est agréable sans avoir à vérifier l'état avant et après l'appel, nous avons échangées cette complexité avec celle d'abonnement et suppression d'abonnements de l'événement de domaine. En outre, le code qui appelle le modèle de domaine dans les cas d'utilisation ne doit pas doit savoir si un client peut devenir par défaut il. Lorsque le code interagit directement avec le client, ce n'est pas tel très important. Mais lorsque vous envoyez une commande, nous peut mettre le stock d'un des produits ordre sous son seuil de réapprovisionnement, nous ne voudrez pas gérer cet événement dans le code, trop.
Il serait mieux si nous peut avoir chaque événement géré par une classe dédiée qui n'a pas traiter les cas d'utilisation spécifique mais a pu être génériquement activée selon les besoins de tous les cas d'utilisation. Voici à quoi ressemblerait une telle classe :
public class CustomerBecamePreferredHandler : Handles<CustomerBecamePreferred>
{
public void Handle(CustomerBecamePreferred args)
{
// send email to args.Customer
}
}
Nous allons parler de quel type d'infrastructure effectue cette classe comme par magie obtenir appelé lorsque cela est nécessaire, mais nous allons voir ce qui est à gauche du code d'origine de la commande Envoyer :
public void SubmitOrder(OrderData data)
{
// call the domain model for regular order submit logic
}
C'est aussi en mode minimal et simple comme un pourrait espère, notre code n'a besoin de rien sur les événements savoir.
Événements de domaine explicite
Dans la classe CustomerBecamePreferredHandler nous voir la référence à un type appelé CustomerBecamePreferred — une représentation explicite dans le code de l'occurrence mentionnée dans la demande. Cette classe peut être aussi simple que celui-ci :
public class CustomerBecamePreferred : IDomainEvent
{
public Customer Customer { get; set; }
}
L'étape suivante consiste à avoir la possibilité de n'importe quelle classe dans notre modèle de domaine de déclencher un tel événement, qui est facilement réalisable avec la suivante classe statique qu'utilise d'un conteneur comme Unity, Castle ou Spring.NET :
public static class DomainEvents
{
public IContainer Container { get; set; }
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach(var handler in Container.ResolveAll<Handles<T>>())
handler.Handle(args);
}
}
Maintenant, toute classe dans notre modèle de domaine peut déclencher un événement domaine, avec les classes entité généralement déclencher les événements comme suit :
public class Customer
{
public void DoSomething()
{
// regular logic (that also makes IsPreferred = true)
DomainEvents.Raise(new CustomerBecamePreferred() { Customer = this });
}
}
Aptitude au test
Alors que la classe DomainEvents affichée est fonctionnelle, il peut rendre un modèle de domaine quelque peu fastidieux de test nous faudra faire unitaire utiliser d'un conteneur pour vérifier ce domaine événements ont été déclenchés. Certains ajouts à la classe DomainEvents peuvent sidestep le problème, comme illustré figure 1 .
La figure 1 ajouts à la classe DomainEvents
public static class DomainEvents
{
[ThreadStatic] //so that each thread has its own callbacks
private static List<Delegate> actions;
public IContainer Container { get; set; } //as before
//Registers a callback for the given domain event
public static void Register<T>(Action<T> callback) where T : IDomainEvent
{
if (actions == null)
actions = new List<Delegate>();
actions.Add(callback);
}
//Clears callbacks passed to Register on the current thread
public static void ClearCallbacks ()
{
actions = null;
}
//Raises the given domain event
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach(var handler in Container.ResolveAll<Handles<T>>())
handler.Handle(args);
if (actions != null)
foreach (var action in actions)
if (action is Action<T>)
((Action<T>)action)(args);
}
}
Maintenant un test unitaire peut être entièrement autonome sans devoir d'un conteneur, en tant que La figure 2 s'affiche.
Test unitaire de la figure 2 sans conteneur
public class UnitTest
{
public void DoSomethingShouldMakeCustomerPreferred()
{
var c = new Customer();
Customer preferred = null;
DomainEvents.Register<CustomerBecamePreferred>(
p => preferred = p.Customer
);
c.DoSomething();
Assert(preferred == c && c.IsPreferred);
}
}
Commandes et requêtes
Les cas d'utilisation que nous avons examen jusqu'à présent ont traité avec modification des données et les règles autour d'elles. Encore dans de nombreux systèmes, les utilisateurs devez également pouvoir afficher ces données, ainsi que pour effectuer toutes sortes de recherche, les tris et les filtres.
J'ai avait initialement pensé que les mêmes classes entité qui figuraient dans le modèle de domaine doivent être utilisées pour afficher des données à l'utilisateur. Au fil des années, j'ai été obtention utilisé pour comprendre que mon original pensent souvent avère incorrect. Le modèle de domaine consiste à encapsuler les données avec les comportements métier.
Affichant les informations utilisateur n'implique aucun comportement Professionnel et est à l'ouverture que les données. Même lorsque nous ont certaines exigences relatives à la sécurité autour de laquelle les utilisateurs peuvent voir les informations, qui peut souvent être représentée comme un filtrage obligatoire des données.
Alors que je était «réussi» dans le passé dans la création d'un modèle unique objet persistant qui commandes et requêtes, il était souvent très difficile d'évoluer, que chaque partie du système tugged le modèle dans une direction différente.
Il s'avère que les développeurs prennent souvent des besoins plus strenuous que l'entreprise a réellement besoin. La décision d'utiliser les entités de modèle de domaine pour afficher des informations à l'utilisateur est juste un exemple.
Vous voyez, dans un système multi-utilisateur, les modifications apportées par un utilisateur n'ont pas nécessairement besoin d'être visible immédiatement à tous les autres utilisateurs. Nous avons tous implicitement comprendre cela lorsque nous allons présenter la mise en cache pour améliorer les performances, mais restent des questions les plus profondes : Si vous ne devez les données plus récentes, pourquoi passer via le modèle de domaine nécessairement fonctionne sur ces données ? Si vous n'avez pas besoin du comportement trouvé sur ces classes de modèle de domaine, pourquoi plough les pour accéder à leurs données ?
Pour ces anciens suffisamment à retenir, les méthodes conseillées autour de l'aide de COM + guidé nous pour créer des composants séparés pour en lecture - uniquement et en lecture-écriture logique. Nous voici, de dix ans plus tard, avec de nouvelles technologies comme Entity Framework, mais les mêmes principes continuent à maintenir.
L'obtention de données d'une base de données et affichage à un utilisateur a un problème relativement facile à résoudre ces jours. Cela peut être aussi simple en utilisant un lecteur de données ADO.NET ou le jeu de données.
La figure 3 présente ce que notre architecture «nouveau».
Figure 3 modèle pour l'obtention de données à partir d'une base de données
Une chose est différente dans ce modèle à partir d'approches courantes en fonction de liaison de données bidirectionnelle, est que la structure qui est utilisée pour afficher les données n'est pas utilisée pour les modifications. Cela permet notamment le suivi des modifications pas entièrement nécessaire.
Dans cette architecture, flux de données haut le côté droit de la base de données à l'utilisateur dans l'écran de requêtes et sur le côté gauche de l'utilisateur dans la base de données sous la forme de commandes. Choix d'accéder à une base de données totalement distinct utilisée pour ces requêtes équivaut une option intéressante en termes de performances et l'évolutivité, lectures n'interfèrent pas avec des écritures dans la base de données (y compris les pages de données restent en mémoire dans la base de données), mais un mécanisme de synchronisation explicite entre les deux est requis. Les options de cette incluent ADO.NET Sync Services, SQL Server Integration Services (SSIS) et messagerie publish/subscribe. Choisir l'un de ces options n'est pas abordée de cet article.
Conserver l'entreprise dans le domaine
Un des défis auxquels font face les développeurs lorsque vous concevez un modèle de domaine est comment s'assurer que logique métier ne fonds hors du modèle de domaine. Il n'existe aucune solution argent puce, mais un style de travail parvient à trouver un équilibre entre d'accès concurrentiel, exactitude et l'encapsulation de domaine qui peut même être testée pour des outils d'analyse statique comme FxCop délicate.
Voici un exemple du type de code que nous ne souhaitez voir interagir avec un modèle de domaine :
public void SubmitOrder(OrderData data)
{
var customer = GetCustomer(data.CustomerId);
var shoppingCart = GetShoppingCart(data.CartId);
if (customer.UnpaidOrdersAmount + shoppingCart.Total > Max)
// fail (no discussion of exceptions vs returns codes here)
else
customer.Purchase(shoppingCart);
}
Bien que ce code soit très orienté objet, nous pouvons voir qu'une certaine quantité de logique métier est effectuée ici plutôt que dans le modèle de domaine. Une solution préférable serait ceci :
public void SubmitOrder(OrderData data)
{
var customer = GetCustomer(data.CustomerId);
var shoppingCart = GetShoppingCart(data.CartId);
customer.Purchase(shoppingCart);
}
En cas de la commande Nouveau dépassant la limite de commandes non payés, qui est représenté par un événement domaine géré par une classe distincte comme indiqué précédemment. L'achat méthode serait provoque pas les modifications de données dans ce cas, aboutissant à une transaction sans effet entreprise techniquement réussie.
Lors de l'inspection de la différence entre les deux exemples de code, nous pouvons voir qu'appelant nécessairement uniquement une méthode unique sur le modèle de domaine signifie que toute la logique métier doit encapsulé il. L'API du modèle de domaine souvent plus ciblé améliore encore aptitude au test.
Bien que ce soit une bonne étape dans la bonne direction, il s'ouvre des questions sur la concurrence.
Accès concurrentiel
Vous consultez dans entre le moment où nous obtenons le client et le temps de que nous lui demander d'effectuer l'achat, une autre transaction peut se présentent sous Modifier le client de telle sorte que son montant impayé de la commande est mis à jour. Qui risque de notre transaction pour effectuer l'achat (basée sur les données extraites précédemment), bien qu'il n'est pas conforme avec l'état mis à jour.
Le plus simple pour résoudre ce problème consiste pour nous entraîner l'enregistrement client à être verrouillé lorsque nous le lire à l'origine, effectuée par indiquant un niveau d'isolation transaction d'au moins-lecture renouvelable (ou sérialisable, qui est la valeur par défaut) comme suit :
public void SubmitOrder(OrderData data)
{
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.RepeatableRead }
))
{
// regular code
}
}
Bien que cela prend un verrou légèrement plus onéreux que le niveau d'isolation read committed certains environnements de haute performance avez réglées, performances peuvent être conservés niveaux similaires lorsque les entités impliquées dans un cas d'utilisation donné sont chargées de façon dynamique et sont connectées par des colonnes indexées. Cela est souvent en grande partie compensé par le modèle de codage applicative beaucoup plus simple, car aucun code n'est requis pour identifier ou résoudre les problèmes d'accès concurrentiel. Lorsque vous utilisez une base de données distinct pour la requête partie du système et toutes les lectures est déchargées de la base de données OLTP desservant le modèle de domaine, performances et évolutivité peuvent être presque identique à lecture validée basés sur les solutions.
Recherche une solution complète
Le modèle de modèle de domaine est en effet un outil puissant dans les mains d'un artisan compétent. Comme de nombreux autres développeurs, la première fois que j'ai choisi jusqu'à cet outil, j'over-used il et peut même ont abusé il avec moins de superbes résultats. Lorsque vous concevez un modèle de domaine, consacrer plus de temps en examinant les détails figurent dans plusieurs cas d'utilisation plutôt que de sauter directement dans la modélisation entité relations — veillez surtout de configuration de ces relations pour les besoins d'afficher les données de l'utilisateur. Qui est mieux pris en charge par interrogation de base de données simple et simple, avec éventuellement une fine couche de façade du dessus pour certains indépendance du fournisseur de base de données.
Lorsqu'en examinant le code en dehors du modèle de domaine interagit avec lui, recherchez l'agile» la plus simple chose qui peut éventuellement fonctionner» — un appel de méthode unique sur un seul objet du domaine, même dans le cas lorsque vous travaillez sur plusieurs objets. Événements de domaine peuvent aider rond votre solution de gestion des interactions plus complexes et des intégrations technologiques, sans introduire de tributaire de.
Lorsque vous démarrez ce chemin d'accès, ça me temps d'ajuster mon raisonnement, mais les avantages de chaque modèle a été pensés rapidement. Lorsque j'a commencé à tous ces modèles utilisant ensemble, j'ai constaté qu'ils fournis une solution complète aux domaines d'entreprise même les plus exigeantes, tout en conservant tout le code dans chaque partie du système de petites, ciblé et testable — tout ce dont un développeur de choix.
Works cités
Fowler, M. Patterns of Enterprise Application Architecture, (Addison Wesley, 2003).
Udi Dahan Reconnu comme un MVP, un architecte principal IASA et Dr. SOA expert Dobb, Udi Dahan est le logiciel Simplist, consultant indépendant, haut-parleur, auteur et formateur fournissant des services haut de gamme architecture orientée service, évolutives et sécurisées et la conception. Contactez Udi via son blog à UdiDahan.com.