Architecture d'une application de service modulaire

11

Je cherche à concevoir une nouvelle solution très modulaire par nature et je voudrais créer une structure qui prend en charge cette conception pour permettre une expansion future facile, une séparation claire des préoccupations, l'octroi de licences par module, etc. La plupart de ce que j'ai que l'on trouve sur le Web à propos des applications modulaires ou composites sont centrées sur l'interface utilisateur, centrées sur Silverlight, WPF, etc. Dans mon cas, je développe une application de service WCF qui sera utilisée par d'autres développeurs travaillant sur divers projets d'interface utilisateur.

Contexte

Le but de mon projet est de créer une source centralisée de logique métier / domaine pour prendre en charge plusieurs de nos applications métier qui dupliquent actuellement les processus, les règles, etc. Et bien que tous les avantages de la modularité ne soient pas atteints, je voudrais saisir la possibilité d'établir un cadre qui tirera parti des éléments dont nous pouvons tirer parti.

J'ai commencé la conception en regardant l'API exposée par l'application de service. Il est clair que les services peuvent être séparés selon les lignes modulaires que j'envisageais. Par exemple, j'aurai des services FinanceService, InventoryService, PersonnelService, etc. qui regroupent mes opérations de service pour fournir une haute cohésion dans l'API tout en gardant le couplage bas afin que les clients n'aient qu'à consommer les services qui sont pertinents pour leur application.

Il est logique pour moi que je puisse alors avoir des modules séparés pour chacun de ces services, tels que MyApp.Finance, MyApp.Inventory, My.Personnel et ainsi de suite. Les préoccupations transversales et les types partagés se trouveraient dans l'assembly partagé MyApp. De là, je suis un peu ligoté.

(Oh, je dois mentionner que j'utiliserai un conteneur IoC pour l'injection de dépendances pour garder l'application faiblement couplée. Je ne mentionnerai pas lequel parce que je ne veux pas ouvrir la boîte de Pandore!)

Dans MyApp.ServiceHost, je vais créer un fichier hôte de service (.svc) correspondant à chaque module, FinanceService.svc par exemple. L'hôte de service a besoin du nom du service qui correspond aux informations du fichier de configuration qui contient l'interface définissant le contrat de service. La configuration IoC est ensuite utilisée pour cartographier l'implémentation concrète de l'interface à utiliser.

1. La couche de service doit-elle implémenter l'API et déléguer aux modules ou les modules doivent-ils être autonomes (en ce qu'ils contiennent tout ce qui concerne ce module, y compris l'implémentation de service)?

Une façon d'aborder le problème est d'avoir un "module" MyApp.Services qui contient l'implémentation des contrats de service. Chaque classe de service délègue simplement à une autre classe dans le module approprié qui contient la logique du domaine pour l'opération. Par exemple, la classe WCS FinanceService dans MyApp.Services délègue à une autre interface qui est implémentée dans le module Finance pour effectuer l'opération. Cela me permettrait de maintenir une façade de service mince et de «brancher» l'implémentation à l'implémentation de service et d'éliminer le besoin pour les modules de se soucier de WCF, par exemple.

D'un autre côté, il est peut-être préférable que chaque module soit autonome en ce qu'il ait l'interface et l'implémentation. L'hôte de service fait référence à l'interface de contrat de service trouvée dans le module et l'IoC est configuré pour utiliser également l'implémentation appropriée à partir du module. Cela signifie que l'ajout d'un nouveau module peut être effectué sans modification de la couche de service autre que l'ajout d'un nouveau fichier .svc et des informations de configuration IoC.

Je pense à l'impact si je passe du WCF standard à une interface de service RESTful ou si je vais aux services RIA ou quelque chose du genre. Si chaque module contient la mise en œuvre du contrat de service, je dois apporter des modifications à chaque module si je change de technologie ou d'approche de service. Mais, si la façade est son propre module, je n'ai qu'à changer cette partie pour faire un changement. Les modules devraient alors implémenter un ensemble de contrats (interfaces) différent, éventuellement défini dans l'assembly partagé ???

2. Quelle est la meilleure façon de gérer le partage des ressources entre les modules et / ou les dépendances entre les modules?

Prenez, par exemple, une opération de réception. À première vue, il est logique que cela entre dans le module Inventaire car la réception des marchandises est une fonction d'inventaire. Cependant, il y a aussi un aspect financier dans la mesure où nous devons générer un reçu et autoriser le paiement.

D'une part, je m'attendrais à utiliser un certain type d'événements / messagerie de domaine pour communiquer l'opération. Le module d'inventaire déclenche un GoodsReceivedEvent qui est géré par le module financier pour générer le reçu et lancer le processus de paiement. Cependant, cela signifie que le module financier doit connaître les articles en stock qui ont été reçus. Je peux simplement y faire référence par ID, mais que se passe-t-il si j'ai besoin d'informations supplémentaires pour le reçu, telles que le nom et / ou la description, le coût unitaire, etc.? Est-il judicieux que chaque module ait sa propre version d'un élément d'inventaire conçu pour répondre aux besoins de ce module? Dans ce cas, le module financier devrait effectuer sa propre recherche des articles en stock lors de la gestion de l'événement GoodsReceivedEvent.

...

J'ai utilisé cet exemple exprès car j'ai beaucoup travaillé avec divers systèmes ERP et je sais qu'ils sont conçus avec ce type de modularité - je ne sais tout simplement pas comment. Je ne le mentionne pas non plus explicitement ci-dessus, mais je préfère suivre les principes de conception pilotée par domaine dans l'architecture de la solution et je pense que ce type de modularité s'intègre parfaitement dans ce domaine.

Toute aide pour enrouler ma tête autour de cela est grandement appréciée.

SonOfPirate
la source
1
Pardon. Impossible de trouver une question dans toute la pensée. Quelle est la question?
S.Lott
1
Recherchez les points d'interrogation. Il y a plusieurs points où j'offre des options, mais ne présumez pas de solution et plusieurs questions spécifiques pour aider à guider le processus sur la bonne voie.
SonOfPirate
1
@SonOfPirate: Désolé. J'espérais que vous prendriez le temps de mettre en évidence ou de souligner les questions afin que d'autres personnes puissent vous aider.
S.Lott
4
Je suis d'accord avec S.Lott. Je suis désolé, mais ce n'est pas une question, c'est un courant de conscience. Nous pouvons vous aider beaucoup mieux si vous résumez cela à une ou deux questions ciblées et essayez de séparer les préoccupations architecturales des détails de mise en œuvre.
Aaronaught
1
@SonOfPirate: "Architecture" concerne la structure et la communication de cette structure aux autres développeurs. Cela commence par la description. Il doit atteindre un niveau de clarté et de transparence qui permet aux autres développeurs de «l'obtenir» totalement et de suivre les principes de conception architecturale dans tout ce qu'ils font.
S.Lott

Réponses:

2

Est-il judicieux que chaque module ait sa propre version d'un élément d'inventaire conçu pour répondre aux besoins de ce module? Dans ce cas, le module financier devrait effectuer sa propre recherche des articles en stock lors de la gestion de l'événement GoodsReceivedEvent. Où est-ce que je fais la distinction entre la modularité et la nécessité de partager des informations?

Vous devrez toujours accepter les compromis. C'est tout ce qu'il y a vraiment à dire à vos questions. Il a été recommandé de conserver les entités dans un assembly séparé, de nombreuses applications les conservent donc dans une bibliothèque partagée. Mais cela signifie qu'il n'y a pas de limites logiques (physiques) métier. Effectuer vos propres recherches signifie beaucoup de code redondant que je ne mettrais en œuvre que si vous avez des exigences particulières dans les deux modules. Bien sûr, en le considérant d'un point de vue architectural, votre module financier et votre module d'inventaire doivent de toute façon partager une dépendance. Le module financier dépend très probablement du module d'inventaire. Si les exigences vous permettent de laisser un module dépendre d'un autre module, je dirais que c'est bien d'encapsuler les entités dans les modules auxquels elles appartiennent le plus. Tu' ll faudra trouver un bon équilibre entre la distinction physique (dépendances partagées) et la maintenance. Trop de dépendances partagées pourraient provoquer un cauchemar de maintenance des versions. De plus, avec un ORM, il y aura des problèmes si vous souhaitez mapper de nouvelles relations avec des entités existantes ou les étendre à partir de modules qui dépendent du module contenant l'entité.

Si vous n'utilisez pas ORM, gardez à l'esprit que tous vos modules partagent probablement la même base de données de toute façon, comme c'est souvent le cas. Vous pouvez utiliser cela comme un terrain d'entente.

Vous pouvez également conserver les données en clair (en termes de CSV / ensembles de résultats, par exemple) et utiliser un service de messagerie central pour informer les modules qui s'y sont inscrits de nouvelles données ainsi que de certaines métadonnées. Cela ne signifie pas de véritables dépendances, mais sacrifie le type de sécurité et de contrats et il peut y avoir des problèmes avec des données mal formées. Je suppose que c'est le nombre de gros systèmes ERP qui le font.

Donc, en bref, vous devrez décider du type d'interface que vous souhaitez. Plus de modularité conduit à moins de contrats et vice versa.

J'utilise probablement une bibliothèque partagée pour mes objets de données et un service de messagerie central avec un modèle de sous-publication, qui me semble la meilleure solution.

Faucon
la source
Egalement d'accord avec cela - messagerie + pub / sub et bibliothèques partagées pour les contrats dto (dépôt de pépites interne). Je me posais les mêmes questions que @SonOfPirate jusqu'à ce que cet article le mette en perspective: msdn.microsoft.com/en-us/library/bb245678.aspx
diegohb