Pourquoi séparer l'accès aux données ?
Dans le livre, je pense que les deux premières pages du chapitre Model Driven Design justifient pourquoi vous voulez abstraire les détails d'implémentation technique de l'implémentation du modèle de domaine.
- Vous souhaitez maintenir une connexion étroite entre le modèle de domaine et le code.
- La séparation des préoccupations techniques permet de prouver que le modèle est pratique à mettre en œuvre.
- Vous voulez que le langage omniprésent s'infiltre dans la conception du système.
Tout cela semble avoir pour but d'éviter un "modèle d'analyse" distinct qui se détache de la mise en œuvre réelle du système.
D'après ce que j'ai compris du livre, il est dit que ce "modèle d'analyse" peut finir par être conçu sans tenir compte de la mise en œuvre du logiciel. Une fois que les développeurs essaient d'implémenter le modèle compris par l'entreprise, ils forment leurs propres abstractions par nécessité, ce qui crée un mur dans la communication et la compréhension.
Dans l'autre sens, les développeurs qui introduisent trop de préoccupations techniques dans le modèle de domaine peuvent également provoquer cette fracture.
On peut donc considérer que la pratique de la séparation des préoccupations, comme la persistance, peut aider à se prémunir contre la divergence de ces modèles de conception et d'analyse. S'il est nécessaire d'introduire des éléments comme la persistance dans le modèle, c'est un signal d'alarme. Le modèle n'est peut-être pas pratique à mettre en œuvre.
Je cite :
"Le modèle unique réduit les risques d'erreur, car la conception est désormais une excroissance directe du modèle soigneusement étudié. La conception, et même le code lui-même, ont la communicabilité d'un modèle."
De la façon dont j'interprète cela, si vous vous retrouvez avec plus de lignes de code traitant de choses comme l'accès aux bases de données, vous perdez cette communicativité.
Si l'accès à une base de données est nécessaire pour des choses comme la vérification de l'unicité, jetez un coup d'œil à :
Udi Dahan : les plus grandes erreurs des équipes lors de l'application de DDD
http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/
sous la rubrique "Toutes les règles ne sont pas égales".
et
Utilisation du modèle de domaine
http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119
sous la rubrique "Scénarios pour ne pas utiliser le modèle de domaine", qui aborde le même sujet.
Comment séparer l'accès aux données
Chargement des données via une interface
La "couche d'accès aux données" a été abstraite grâce à une interface, que vous appelez pour récupérer les données requises :
var orderLines = OrderRepository.GetOrderLines(orderId);
foreach (var line in orderLines)
{
total += line.Price;
}
Avantages : L'interface sépare le code de plomberie de l'accès aux données, ce qui vous permet de continuer à écrire des tests. L'accès aux données peut être géré au cas par cas, ce qui permet d'obtenir de meilleures performances qu'avec une stratégie générique.
Contre : Le code d'appel doit supposer ce qui a été chargé et ce qui ne l'a pas été.
Pour des raisons de performance, GetOrderLines renvoie des objets OrderLine avec une propriété ProductInfo nulle. Le développeur doit avoir une connaissance approfondie du code derrière l'interface.
J'ai essayé cette méthode sur des systèmes réels. Vous finissez par changer la portée de ce qui est chargé tout le temps dans une tentative de résoudre les problèmes de performance. Vous finissez par jeter un coup d'œil derrière l'interface pour regarder le code d'accès aux données afin de voir ce qui est chargé et ce qui ne l'est pas.
Or, la séparation des préoccupations devrait permettre au développeur de se concentrer sur un seul aspect du code à la fois, dans la mesure du possible. La technique de l'interface supprime la question de savoir COMMENT ces données sont chargées, mais pas celle de savoir QUELLE QUANTITÉ de données est chargée, QUAND elle est chargée et OÙ elle est chargée.
Conclusion : Une séparation assez faible !
Chargement paresseux
Les données sont chargées à la demande. Les appels pour charger les données sont cachés dans le graphe d'objets lui-même, où l'accès à une propriété peut provoquer l'exécution d'une requête sql avant de renvoyer le résultat.
foreach (var line in order.OrderLines)
{
total += line.Price;
}
Pour : Le "QUAND, OÙ et COMMENT" de l'accès aux données est caché au développeur qui se concentre sur la logique du domaine. Il n'y a pas de code dans l'agrégat qui traite du chargement des données. La quantité de données chargée peut être la quantité exacte requise par le code.
Contre : Lorsque vous êtes confronté à un problème de performance, il est difficile de le résoudre lorsque vous avez une solution générique "taille unique". Le chargement paresseux peut entraîner une baisse générale des performances, et sa mise en œuvre peut être délicate.
Interface avec le rôle/la recherche
Chaque cas d'utilisation est rendu explicite par un Interface des rôles mis en œuvre par la classe d'agrégat, ce qui permet de gérer les stratégies de chargement des données en fonction des cas d'utilisation.
La stratégie de récupération peut ressembler à ceci :
public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
Order Load(string aggregateId)
{
var order = new Order();
order.Data = GetOrderLinesWithPrice(aggregateId);
return order;
}
}
Alors votre agrégat peut ressembler à :
public class Order : IBillOrder
{
void BillOrder(BillOrderCommand command)
{
foreach (var line in this.Data.OrderLines)
{
total += line.Price;
}
etc...
}
}
La stratégie BillOrderFetchingStrategy est utilisée pour construire l'agrégat, puis l'agrégat fait son travail.
Pour : Permet un code personnalisé par cas d'utilisation, ce qui permet des performances optimales. Est en ligne avec le Principe de ségrégation des interfaces . Aucune exigence de code complexe. Les tests unitaires des agrégats n'ont pas à imiter la stratégie de chargement. Une stratégie de chargement générique peut être utilisée pour la majorité des cas (par exemple, une stratégie "load all") et des stratégies de chargement spéciales peuvent être mises en œuvre si nécessaire.
Inconvénient : le développeur doit toujours ajuster/réviser la stratégie de récupération après avoir modifié le code du domaine.
Avec l'approche de la stratégie d'extraction, vous pouvez toujours vous retrouver à modifier le code d'extraction personnalisé en cas de changement des règles de gestion. Il ne s'agit pas d'une séparation parfaite des préoccupations, mais elle sera plus facile à maintenir et est meilleure que la première option. La stratégie d'extraction encapsule le COMMENT, le QUAND et le OÙ des données sont chargées. Elle assure une meilleure séparation des préoccupations, sans perdre la flexibilité de l'approche de chargement paresseux à taille unique.
0 votes
Jetez un coup d'œil à ma question stackoverflow.com/q/8269784/235715 Il s'agit d'une situation où il est difficile de capturer la logique, sans que l'entité n'ait accès au référentiel. Bien que je pense que les entités ne devraient pas avoir accès aux référentiels, et qu'il y a une solution à ma situation lorsque le code peut être réécrit sans référence au référentiel, mais pour le moment je n'en vois pas.
1 votes
Je ne sais pas d'où il vient. Ce que j'en pense : Je pense que ce malentendu vient de personnes qui ne comprennent pas ce qu'est le DDD. Cette approche ne sert pas à mettre en œuvre des logiciels, mais à les concevoir (domaine conception). À l'époque, nous avions des architectes et des implémenteurs, mais aujourd'hui, il n'y a plus que des développeurs de logiciels. DDD est destiné aux architectes. Et lorsqu'un architecte conçoit un logiciel, il a besoin d'un outil ou d'un modèle pour représenter une mémoire ou une base de données pour les développeurs qui mettront en œuvre la conception préparée. Mais la conception elle-même (d'un point de vue commercial) n'a pas besoin d'un référentiel.