ORM et couches

Désolé pour ce point étant partout ici … mais je me sens comme un chien poursuivant ma queue et je suis tout confus à ce point.

J’essaie de voir le moyen le plus propre de développer une solution à 3 niveaux (IL, BL, DL) dans laquelle le DL utilise un ORM pour obtenir un access abstrait à une firebase database.

Partout où je suis allé, les gens utilisent LinqToSQL ou LLBLGen Pro pour générer des objects représentant les tables de firebase database et se réfèrent à ces classes dans les 3 couches. On dirait que 40 années de modèles de codage ont été ignorées – ou un changement de paradigme s’est produit, et j’ai manqué la partie expliquant pourquoi il est parfaitement correct de le faire.

Pourtant, il semble qu’il y ait encore des raisons de vouloir être agnostique dans le mécanisme de stockage de données – regardez ce qui vient de se passer pour LinqToSQL: beaucoup de code a été écrit contre cela – seulement pour que MS puisse le laisser tomber … Je voudrais donc isoler la partie ORM du mieux que je peux, mais je ne sais pas comment.

Donc, pour en revenir à l’essentiel, voici les éléments de base que je souhaite avoir assemblés de manière très propre:

Les assemblages de départ: UL.dll BL.dll DL.dll

Les classes principales:

Une classe Message qui possède une collection exposant les propriétés (appelée MessageAddresses) d’objects MessageAddress:

class Message { public MessageAddress From {get;} public MessageAddresses To {get;} } 

Les fonctions par couche:

Le BL expose une méthode à l’interface utilisateur appelée GetMessage (Guid id) qui renvoie une instance de Message.

Le BL à son tour enveloppe le DL.

Le DL a une ProviderFactory qui encapsule une instance de fournisseur. DL.ProviderFactory expose (éventuellement … une partie de mes questions) deux méthodes statiques appelées GetMessage (Guid ID) et SaveMessage (message) Le but ultime serait de pouvoir échanger un fournisseur écrit pour Linq2SQL par un pour LLBLGen Pro ou un autre fournisseur qui ne fonctionne pas avec un ORM (par exemple, VistaDB).

Objectifs de conception: Je souhaite la séparation des couches. Je voudrais que chaque couche ait uniquement une dépendance sur la couche inférieure, plutôt que supérieure. Je voudrais que les classes générées par ORM soient uniquement dans la couche DL. J’aimerais que UL partage la classe Message avec BL.

Par conséquent, cela signifie-t-il que:

a) Le message est défini dans BL. b) La représentation Db / Orm / Manual de la table de firebase database (“DbMessageRecord” ou “MessageEntity”, ou ce que l’ORM l’appelle autrement) est définie dans DL. c) BL est dépendant de DL d) Avant d’appeler des méthodes DL n’ayant pas de référence ou de connaissance sur BL, le BL doit les convertir en entités BL (par exemple: DbMessageRecord)?

UL:

 Main() { id = 1; Message m = BL.GetMessage(id); Console.Write (ssortingng.Format("{0} to {1} recipients...", m.From, m.To.Count)); } 

BL:

 static class MessageService { public static Message GetMessage(id) { DbMessageRecord message = DLManager.GetMessage(id); DbMessageAddressRecord[] messageAddresses = DLManager.GetMessageAddresses(id); return MapMessage(message, } protected static Message MapMessage(DbMessageRecord dbMessage. DbMessageAddressRecord[] dbAddresses) { Message m = new Message(dbMessage.From); foreach(DbMessageAddressRecord dbAddressRecord in dbAddresses){ m.To.Add(new MessageAddress (dbAddressRecord.Name, dbAddressRecord.Address); } } 

DL:

 static class MessageManager { public static DbMessageRecord GetMessage(id); public static DbMessageAddressRecord GetMessageAddresses(id); } 

Questions: a) Évidemment, cela représente beaucoup de travail tôt ou tard. b) Plus de bugs c) Plus lent d) Depuis que BL dépend maintenant de DL et référence des classes dans DL (par exemple, DbMessageRecord), il semble que, comme elles sont définies par ORM, vous ne pouvez pas extraire un fournisseur et le remplacer. avec un autre, … ce qui rend tout l’exercice inutile … pourrait aussi bien utiliser les classes de l’ORM tout au long de la BL. e) Ou … un autre assemblage est nécessaire entre le BL et le DL et un autre mappage est nécessaire afin de laisser BL indépendant des classes DL sous-jacentes.

J’aimerais pouvoir poser les questions plus clairement … mais je suis vraiment perdu à ce stade. Toute aide serait grandement appréciée.

c’est un peu partout et me rappelle mes premières incursions dans orm et DDD. J’utilise personnellement des objects de domaine principaux, des objects de messagerie, des gestionnaires de messages et des référentiels. Ainsi, mon interface utilisateur envoie un message à un gestionnaire qui hydrate ensuite mes objects via des référentiels et exécute la logique métier de cet object de domaine. J’utilise NHibernate pour mon access aux données et FluentNHibernate pour une liaison typée plutôt que la configuration. Goosey. Hbm.

Ainsi, la messagerie est tout ce qui est partagé entre mon interface utilisateur et mes gestionnaires et tout le BL est sur le domaine.

Je sais que je pourrais m’être ouvert à la punition pour mon explication, si ce n’est pas clair que je défendrai plus tard.

Personnellement, je ne suis pas un grand fan d’objects générés par code.

Je dois continuer à append sur cette réponse. Essayez de considérer votre messagerie comme une commande plutôt que comme une entité de données représentant votre firebase database. Je vais vous donner un exemple d’une de mes classes simples et d’une décision d’infrastructure qui a très bien fonctionné pour moi et pour laquelle je ne peux pas prendre le crédit pour:

 [Serializable] public class AddMediaCategoryRequest : IRequest { private readonly Guid _parentCategory; private readonly ssortingng _label; private readonly ssortingng _description; public AddMediaCategoryRequest(Guid parentCategory, ssortingng label, ssortingng description) { _parentCategory = parentCategory; _description = description; _label = label; } public ssortingng Description { get { return _description; } } public ssortingng Label { get { return _label; } } public Guid ParentCategory { get { return _parentCategory; } } } [Serializable] public class AddMediaCategoryResponse : Response { public Guid ID; } public interface IRequest : IRequest where T : Response, new() {} [Serializable] public class Response { protected bool _success; private ssortingng _failureMessage = "This is the default error message. If a failure has been reported, it should have overwritten this message."; private Exception _exception; public Response() { _success = false; } public Response(bool success) { _success = success; } public Response(ssortingng failureMessage) { _failureMessage = failureMessage; } public Response(ssortingng failureMessage, Exception exception) { _failureMessage = failureMessage; _exception = exception; } public bool Success { get { return _success; } } public ssortingng FailureMessage { get { return _failureMessage; } } public Exception Exception { get { return _exception; } } public void Failed(ssortingng failureMessage) { _success = false; _failureMessage = failureMessage; } public void Failed(ssortingng failureMessage, Exception exception) { _success = false; _failureMessage = failureMessage; _exception = exception; } } public class AddMediaCategoryRequestHandler : IRequestHandler { private readonly IMediaCategoryRepository _mediaCategoryRepository; public AddMediaCategoryRequestHandler(IMediaCategoryRepository mediaCategoryRepository) { _mediaCategoryRepository = mediaCategoryRepository; } public AddMediaCategoryResponse HandleRequest(AddMediaCategoryRequest request) { MediaCategory parentCategory = null; MediaCategory mediaCategory = new MediaCategory(request.Description, request.Label,false); Guid id = _mediaCategoryRepository.Save(mediaCategory); if(request.ParentCategory!=Guid.Empty) { parentCategory = _mediaCategoryRepository.Get(request.ParentCategory); parentCategory.AddCategoryTo(mediaCategory); } AddMediaCategoryResponse response = new AddMediaCategoryResponse(); response.ID = id; return response; } } 

Je sais que cela continue, mais ce système de base m’a très bien servi au cours de la dernière année.

vous pouvez voir que le gestionnaire permet à l’object de domaine de gérer la logique propre au domaine

Le concept qui vous semble manquer est IoC / DI (c.-à-d. Inversion of Control / Dependency Injection). Au lieu d’utiliser des méthodes statiques, chacune de vos couches ne devrait dépendre que d’une interface de la couche suivante, l’instance réelle étant injectée dans le constructeur. Vous pouvez appeler votre liste de dissortingbution un référentiel, un fournisseur ou tout autre élément, à condition qu’il s’agisse d’une abstraction propre du mécanisme de persistance sous-jacent.

En ce qui concerne les objects qui représentent les entités (en gros mappage sur des tables), il est fortement déconseillé d’avoir deux ensembles d’objects (l’un spécifique à la firebase database et l’autre non). Il est normal qu’ils soient référencés par les trois couches tant qu’ils sont des POCO (ils ne devraient pas vraiment savoir qu’ils sont persistés), ou même des DTO (structures pures sans aucun comportement). En faire des DTO correspond mieux à votre concept de BL, mais je préfère que ma logique métier soit répartie sur mes objects de domaine (“style OOP”) plutôt que d’avoir la notion de BL (“style Microsoft”).

Pas sûr de Llblgen, mais NHibernate + tout IoC comme SpringFramework.NET ou Windsor fournit un modèle assez propre qui le supporte.

C’est probablement une réponse trop indirecte, mais l’année dernière, j’ai lutté contre ce type de questions dans le monde de Java et j’ai trouvé l’ architecture Patterns of Enterprise Application Architecture de Martin Fowler très utile (voir aussi son catalogue d’échantillons ). Bon nombre des modèles traitent des mêmes problèmes que vous rencontrez. Elles sont toutes joliment abstraites et m’ont aidé à organiser ma reflection de manière à pouvoir voir le problème à un niveau supérieur.

J’ai choisi une approche utilisant le mappeur SQL d’iBatis pour encapsuler nos interactions avec la firebase database. (Un mappeur SQL pilote le modèle de données de langage de programmation à partir des tables SQL, alors qu’un ORM comme le vôtre fonctionne dans l’autre sens.) Le mappeur SQL renvoie des listes et des hiérarchies d’objects de transfert de données, chacune représentant une ligne d’un résultat de requête. Les parameters des requêtes (et des insertions, mises à jour, suppressions) sont également transmis en tant que DTO. La couche BL effectue des appels sur le mappeur SQL (lance cette requête, fait cette insertion, etc.) et transmet les DTO. Les DTO accèdent à la couche de présentation où ils pilotent les mécanismes de développement de modèles générant des représentations XHTML, XML et JSON des données. Ainsi, pour nous, la seule dépendance à la liste de dissortingbution qui a été transmise à l’UI était l’ensemble des DTO, mais elle l’a beaucoup simplifiée davantage qu’à transmettre des valeurs de champ non compressées.

Si vous associez le livre de Fowler à l’aide spécifique que d’autres affiches peuvent apporter, vous vous en sortirez bien. C’est un domaine avec beaucoup d’outils et d’expérience, il devrait donc y avoir beaucoup de bonnes voies à suivre.

Edit: @ Ciel, vous avez tout à fait raison, une instance DTO est juste un POCO (ou dans mon cas un Java POJO). Un DTO de personne pourrait avoir un champ prenom de “Jim” et ainsi de suite. Chaque DTO correspond en gros à une ligne d’une table de firebase database et constitue simplement un ensemble de champs, rien de plus. Cela signifie qu’il n’est pas étroitement associé à la liste de dissortingbution et qu’il est parfaitement approprié de passer à l’interface utilisateur. Fowler en parle à la p. 401 (pas un mauvais premier motif pour couper les dents).

Maintenant, je n’utilise pas d’ORM, qui prend vos objects de données et crée la firebase database. J’utilise un mappeur SQL, qui est un moyen très efficace et pratique de mettre en package et d’exécuter des requêtes de firebase database en SQL. J’ai d’abord conçu mon SQL (je le connais assez bien), puis j’ai conçu mes DTO, puis j’ai configuré ma configuration iBatis de manière à ce que “select * from Person où personid = # personid #” me renvoie une liste Java. d’objects Personne DTO. Je n’ai pas encore utilisé d’ORM (Hibernate, par exemple, dans le monde Java), mais avec l’un de ceux-ci, vous créez d’abord vos objects de modèle de données et la firebase database est construite à partir d’eux.

Si vos objects de modèle de données ont toutes sortes de modules complémentaires spécifiques à l’ORM, je comprends pourquoi vous réfléchissez à deux fois avant de les exposer jusqu’à la couche d’interface utilisateur. Mais là, vous pouvez créer une interface C # qui définit uniquement les méthodes get et set POCO, l’utiliser dans toutes vos API non DL, et créer une classe d’implémentation contenant tous les éléments spécifiques à ORM:

 interface Person ... class ORMPerson : Person ... 

Ensuite, si vous modifiez votre ORM ultérieurement, vous pouvez créer d’autres implémentations de POCO:

 class NewORMPerson : Person ... 

et cela n’affectera que votre code de couche DL, car votre code BL et votre interface utilisateur utilise Personne.

@Zvolkov (ci-dessous) suggère d’adopter cette approche de “codage aux interfaces et non aux implémentations” jusqu’au niveau suivant, en vous recommandant d’écrire votre application de telle sorte que tout votre code utilise des objects Infrastructure d’dependency injection pour configurer dynamicment votre application afin qu’elle crée ORMPersons ou NewORMPersons en fonction de l’ORM que vous souhaitez utiliser ce jour-là.

Essayez de centraliser tous les access aux données en utilisant un modèle de référentiel. En ce qui concerne vos entités, vous pouvez essayer d’implémenter une sorte de couche de traduction qui mappera vos entités afin que votre application ne soit pas endommagée. Ceci est juste temporaire et vous permettra de refactoriser lentement votre code.

évidemment, je ne connais pas toute l’étendue de votre base de code, alors considérez la douleur et le gain.

Mon avis seulement, YMMV.

Quand je m’occupe de toute nouvelle technologie, je suppose qu’elle devrait répondre à deux critères sinon je perds mon temps. (Ou je ne le comprends pas assez bien.)

  1. Cela devrait simplifier les choses ou, dans le pire des cas, les rendre plus compliquées.

  2. Il ne devrait pas augmenter le couplage ni réduire la cohésion.

On dirait que vous sentez que vous vous dirigez dans la direction opposée, ce que je sais n’est pas l’intention de LINQ ou des ORM.

Ma propre perception de la valeur de ce nouveau produit est qu’elle aide un développeur à déplacer la limite entre la liste de dissortingbution et la liste de diffusion dans un territoire un peu plus abstrait. Le DL ressemble moins à des tables brutes qu’à des objects. C’est tout. (Je travaille habituellement assez dur pour le faire de toute façon avec un SQL un peu plus lourd et des procédures stockées, mais je suis probablement plus à l’aise avec SQL que la moyenne). Mais si LINQ et ORM ne vous aident pas encore avec ceci, je dirais: continuez, mais c’est là que se trouve le bout du tunnel; simplification et déplacer un peu la limite d’abstraction.