Présentation
Dans les articles précédents, nous avons décrit: la portée , les fondements méthodologiques , un exemple d'architecture et de structure . Dans cet article, je voudrais expliquer comment décrire les processus, les principes de collecte des exigences, en quoi les exigences métier diffèrent des exigences fonctionnelles, comment passer des exigences au code. Parlez des principes d'utilisation du cas d'utilisation et de la façon dont ils peuvent nous aider. Explorez des exemples d'options d'implémentation pour les modèles de conception d'interaction et de couche de service.

Les exemples donnés dans l'article sont donnés à l'aide de notre solution LunaPark , il vous aidera dans les premières étapes des approches décrites.
Séparez les exigences fonctionnelles des exigences commerciales.
Encore et encore, il arrive que de nombreuses idées commerciales ne se transforment pas vraiment en produit final prévu. Cela est souvent dû à une incapacité à comprendre la différence entre les exigences commerciales et les exigences fonctionnelles, ce qui conduit finalement à une collecte inadéquate des exigences, une documentation inutile, des retards de projet et des échecs majeurs de projet.
Ou parfois, nous sommes confrontés à des situations dans lesquelles, bien que la solution finale réponde aux besoins des clients, mais en quelque sorte les objectifs commerciaux ne sont pas atteints.
Par conséquent, il est impératif de séparer les exigences métier des exigences fonctionnelles jusqu'à ce que vous commenciez à les définir. Prenons un exemple.
Supposons que nous rédigions une demande pour une entreprise de livraison de pizza et que nous avons décidé de créer un système de suivi des courriers. Les exigences commerciales sont les suivantes:
"Introduire un système basé sur le Web et un système de suivi des employés mobile basé sur les employés qui capture les courriers sur leurs itinéraires et améliore l'efficacité en surveillant l'activité des courriers, leur absence du travail et la productivité du travail."
Ici, nous pouvons distinguer un certain nombre de caractéristiques qui indiqueront que ce sont des exigences de l'entreprise:
- les exigences commerciales sont toujours écrites du point de vue du client;
- Ce sont des exigences larges et de haut niveau, mais toujours orientées vers les parties;
- ce ne sont pas des objectifs de l'entreprise, mais ils aident l'entreprise Ă atteindre ses objectifs;
- répondez aux questions « pourquoi » et « quoi ». Que souhaite recevoir l'entreprise? Et pourquoi en a-t-elle besoin?
Les exigences fonctionnelles sont des actions que le système doit effectuer pour implémenter les exigences métier. Ainsi, les exigences fonctionnelles sont liées à la solution ou au logiciel développé. Nous formulons les exigences fonctionnelles pour l'exemple ci-dessus:
- le système devrait afficher la longitude et la latitude de l'employé via GPS / GLONASS;
- le système devrait afficher les positions des employés sur la carte;
- le système devrait permettre aux gestionnaires d'envoyer des notifications à leurs subordonnés sur le terrain.
Nous mettons en évidence les fonctionnalités suivantes:
- les exigences fonctionnelles sont toujours écrites du point de vue du système;
- ils sont plus spécifiques et détaillés;
- c'est grâce à la satisfaction des exigences fonctionnelles qu'une solution efficace est développée qui répond aux besoins de l'entreprise et aux objectifs du client;
- répondre à la question « comment ». Comment le système résout les besoins de l'entreprise.
Il convient de dire quelques mots sur les exigences non fonctionnelles (également appelées «exigences de qualité»), qui imposent des restrictions sur la conception ou la mise en œuvre (par exemple, les exigences de performances, de sécurité, de disponibilité, de fiabilité). Ces exigences répondent à la question « quoi » devrait être le système.
Le développement est la traduction des exigences métier en exigences fonctionnelles. La programmation appliquée est la mise en œuvre d'exigences fonctionnelles et le système - non fonctionnel.
Cas d'utilisation
La mise en œuvre des exigences fonctionnelles est souvent la plus complexe dans les systèmes commerciaux. Dans une architecture pure, les exigences fonctionnelles sont implémentées via la couche Use Case .
Mais pour commencer, je veux me tourner vers la source. Ivar Jacobson - l'auteur de la définition de cas d'utilisation , l'un des auteurs d'UML, et de la méthodologie RUP, dans son article Use-Case 2.0 The Hub of Software Development identifie 6 principes d'utilisation des cas d' utilisation:
- les rendre simples grâce à la narration;
- avoir un plan stratégique, être conscient de la situation dans son ensemble;
- se concentrer sur le sens;
- aligner le système en couches;
- livrer le système étape par étape;
- répondre aux besoins de l'équipe.
Nous considérons brièvement chacun de ces principes, ils nous sont utiles pour une meilleure compréhension. Ci-dessous ma traduction gratuite, avec abréviations et encarts, je vous recommande fortement de vous familiariser avec l'original.
Simplicité grâce à la narration
La narration fait partie de notre culture; C'est le moyen le plus simple et le plus efficace de transférer des connaissances, des informations d'une personne à une autre. C'est le meilleur moyen de communiquer ce que le système doit faire et d'aider l'équipe à se concentrer sur des objectifs communs.
Les cas d'utilisation reflètent les objectifs du système. Pour comprendre le cas d'utilisation, nous racontons, racontons une certaine histoire. L'histoire raconte comment atteindre un objectif et comment résoudre les problèmes qui se posent en cours de route. Les cas d'utilisation, comme un livre d'histoires, fournissent un moyen d'identifier et de couvrir toutes les histoires différentes mais liées d'une manière simple et complète. Cela facilite la collecte, la distribution et la compréhension des exigences du système.
Ce principe est en corrélation avec le langage Ubiques de l'approche DDD.
Comprendre toute l'image
Quel que soit le système que vous développez, grand, petit, logiciel, matériel ou entreprise, il est très important de comprendre la situation dans son ensemble. Sans comprendre le système dans son ensemble, vous ne pouvez pas prendre les bonnes décisions sur ce qu'il faut inclure dans le système, ce qu'il faut exclure, combien cela coûtera et quels avantages il apportera.
Ivar Jacobson suggère d'utiliser le diagramme de cas d'utilisation , qui est très pratique pour collecter les exigences. Si les exigences sont compilées et claires, la carte de contexte d'Eric Evans est la meilleure option. Souvent, l'approche Scrum est interprétée de manière à ce que les gens ne passent pas de temps sur un plan stratégique, envisageant de planifier, plus de deux semaines plus tard, une relique du passé. La propagande de Jeff Sutherland est tombée sur le débit d'eau, et les personnes qui ont suivi des cours de formation de deux semaines pour les Scrum Masters autorisés à gérer des projets ont fait leur travail. Mais le bon sens reconnaît l'importance de la planification stratégique. Il n'est pas nécessaire d'élaborer un plan stratégique détaillé, mais cela devrait l'être.
Focus sur la valeur
Lorsque vous essayez de comprendre comment le système sera utilisé, il est toujours important de se concentrer sur la valeur qu'il apportera à ses utilisateurs et aux autres parties intéressées. La valeur est formée uniquement lorsque le système est utilisé. Par conséquent, il est préférable de se concentrer sur la façon dont le système sera appliqué que sur de longues listes de fonctionnalités ou de capacités qu'il peut offrir.
Les cas d'utilisation fournissent cette orientation, vous aidant à vous concentrer sur la façon dont le système sera utilisé par un utilisateur spécifique pour atteindre son objectif. Les cas d'utilisation couvrent de nombreuses façons d'utiliser le système: ceux qui atteignent leurs objectifs avec succès et ceux qui résolvent les difficultés qui surviennent.
De plus, l'auteur donne un merveilleux schéma, auquel il convient d'accorder la plus grande attention:

Le diagramme montre un cas d'utilisation, "Retrait d'espèces à un guichet automatique". La façon la plus simple d'atteindre l'objectif est décrite dans la direction de base (flux de base). D'autres cas sont décrits comme des flux alternatifs. Ces instructions aident à raconter des histoires, à structurer le système et à rédiger des tests.
Superposition
La plupart des systèmes nécessitent beaucoup de travail avant d'être prêts à l'emploi. Ils ont de nombreuses exigences, dont la plupart dépendent d'autres exigences, elles doivent être mises en œuvre avant que les exigences soient satisfaites et évaluées.
C'est une grosse erreur de créer un tel système à la fois. Le système doit être construit à partir de pièces dont chacune a une valeur claire pour les utilisateurs.
Ces idées résonnent avec des approches agiles et avec des idées de domaine .
Lancement de produit étape par étape
La plupart des systèmes logiciels ont évolué au fil des générations. Ils ne sont pas produits à la fois; ils sont construits comme une série de versions, chacune étant basée sur une version précédente. Même les versions elles-mêmes ne sortent pas immédiatement, mais se développent à travers une série de versions intermédiaires. Chaque étape fournit une version claire et utilisable du système. C'est ainsi que tous les systèmes doivent être créés.
Répondre aux besoins de l'équipe
Malheureusement, il n'y a pas de solution universelle aux problèmes de développement logiciel; différentes équipes et différentes situations nécessitent différents styles et différents niveaux de détail. Peu importe les méthodes et techniques que vous choisissez, vous devez vous assurer qu'elles sont suffisamment adaptables pour répondre aux besoins actuels de l'équipe.
Eric Evans dans son livre vous invite à ne pas passer beaucoup de temps à décrire tous les processus via UML. Il suffit d'utiliser n'importe quel schéma visuel. Différentes équipes, différents projets nécessitent un niveau de détail différent, comme l'auteur UML en parle lui-même.
Implémentation
En architecture pure, Robert Martin définit les cas d'utilisation suivants:
Ces cas d'utilisation orchestrent le flux de données vers et depuis les entités, et demandent à ces entités d'utiliser leurs règles métier critiques pour atteindre les objectifs du cas d'utilisation.
Essayons de traduire ces idées en code. Rappelons le schéma du troisième principe d'utilisation des cas d' utilisation et prenons-le comme base. Prenons un processus d'entreprise vraiment complexe: «Faire cuire une tarte au chou».
Essayons de le décomposer:
- vérifier la disponibilité des produits;
- les retirer du stock;
- pétrir la pâte;
- laissez lever la pâte;
- préparer la garniture;
- faire une tarte;
- cuire une tarte.
Nous implémentons cette séquence entière via l' interacteur , et chaque étape sera implémentée via une fonction ou un objet fonctionnel sur la couche de service.
Séquence d'actions (Interacteur)

Je recommande fortement de commencer le développement d'un processus métier complexe avec la séquence d'actions . Plus précisément, pas le cas, vous devez déterminer le domaine de domaine auquel appartient le processus métier. Clarifiez toutes les exigences commerciales. Identifiez toutes les entités impliquées dans le processus. Documentez les exigences et les définitions de chaque entité dans la base de connaissances.
Peignez tout sur papier par étapes. Parfois, vous avez besoin d'un diagramme de séquence. Son auteur est le même qui a inventé le cas d'utilisation - Ivar Jacobson. Le schéma a été inventé par lui lorsqu'il développait un système de maintenance du réseau téléphonique pour Erickson, basé sur le circuit de relais. J'aime beaucoup ce diagramme, et le terme séquence , à mon avis, est plus expressif que le terme interacteur . Mais compte tenu de la plus grande prévalence de ce dernier, nous utiliserons le terme familier - Interacteur .
Un petit indice lorsque vous décrivez un processus d'entreprise est une bonne aide pour vous, la règle principale de la gestion des documents peut devenir: «À la suite de toute activité commerciale, un document doit être établi». Par exemple, nous développons un système de rabais. En accordant une remise, nous, en effet, du point de vue des affaires, concluons un accord entre l'entreprise et le client. Toutes les conditions doivent être énoncées dans ce contrat. Autrement dit, dans le domaine DiscountSystem, vous aurez Entites :: Contract. Ne liez pas la remise au client, mais créez un contrat d' entité , qui décrit les règles de sa fourniture.
Revenons à la description de notre processus d'affaires, après qu'il soit devenu transparent pour toutes les personnes impliquées dans son développement, et que toutes vos connaissances soient fixes. Je vous recommande de commencer à écrire du code avec la séquence d'actions .
Le modèle de conception de séquence est responsable de:
- séquence d' actions ;
- coordination des données transmises entre les actions ;
- les erreurs de traitement commises par les Actions lors de leur exécution;
- retour du résultat de l'ensemble des Actions engagées;
- IMPORTANT : la responsabilité la plus importante de ce modèle de conception est la mise en œuvre de la logique métier.
Je voudrais m'attarder sur la dernière responsabilité plus en détail si nous avons une sorte de processus complexe - nous devons le décrire de manière à ce que ce qui se passe soit clair sans entrer dans les détails techniques. Vous devez le décrire aussi expressivement que vos compétences en programmation vous le permettent . Confiez ce cours au membre le plus expérimenté de votre équipe.
Revenons à la tarte: essayons de décrire le processus de sa préparation via Interactor .
Implémentation
Je donne un exemple d'implémentation avec notre solution LunaPark , que nous avons présentée dans un article précédent.
module Kitchen module Sequences class CookingPieWithabbage < LunaPark::Interactors::Sequence TEMPERATURE = Values::Temperature.new(180, unit: :cel) def call! Services::CheckProductsAvailability.call list: ingredients dough = Services::BeatDough.call from: Repository::Products.get(beat_ingredients) filler = Services::MakeabbageFiller.call from: Repository::Products.get(filler_ingredients) pie = Services::MakePie.call dough, with: filler bake = Services::BakePie.new pie, temp: TEMPERATURE sleep 5.min until bake.call pie end private attr_accessor :beat_ingredients, :filler_ingredients attr_accessor :pie def ingredients_list beat_ingredients_list + filler_ingredients_list end end end end
Comme nous pouvons le voir, l' call!
décrit toute la logique commerciale du processus de cuisson des gâteaux. Et il est pratique à utiliser pour comprendre la logique de l'application.
En outre, nous pouvons facilement décrire le processus de cuisson de la tarte au poisson en remplaçant MakeabbageFiller
par MakeFishFiller
. Ainsi, nous modifions très rapidement le processus métier, sans modifications significatives du code. De plus, nous pouvons laisser les deux séquences en même temps, en adaptant les analyses de rentabilisation.
Arrangements
- Méthode d'
call!
est une méthode obligatoire, elle décrit l'ordre des actions . - Chaque paramètre d'initialisation peut être décrit via un setter ou
attr_acessor
:
class Foo < LunaPark::Interactors::Sequence
- Le reste des méthodes doit être privé.
Exemple d'utilisation
beat_ingredients = [ Entity::Product.new :flour, 500, :gr, Entity::Product.new :oil, 50, :gr, Entity::Product.new :salt, 1, :spoon, Entity::Product.new :milk, 150, :ml, Entity::Product.new :egg, 1, :unit, Entity::Product.new :yeast, 1, :spoon ] filler_ingredients = [ Entity::Product.new :cabbage, 500, :gr, Entity::Product.new :salt, 1, :spoon, Entity::Product.new :pepper, 1, :spoon ] cooking = CookingPieWithabbage.call( beat_ingredients: beat_ingredients, filler_ingredients: filler_ingredients )
Le processus est représenté par l'objet et nous avons toutes les méthodes nécessaires pour l'appeler - l'appel a-t-il réussi, une erreur s'est-elle produite lors de l'appel, et si oui, laquelle?
Gestion des erreurs
Si nous rappelons maintenant le troisième principe de l'application Cas d'utilisation, nous faisons attention au fait qu'en plus de la ligne principale , nous avions également des directions alternatives . Ce sont des erreurs que nous devons gérer. Prenons un exemple: nous ne voulons certainement pas que les événements se déroulent de cette façon, mais nous ne pouvons rien y faire, la dure réalité est que les tartes brûlent périodiquement.
Interactor intercepte toutes les erreurs héritées de la LunaPark::Errors::Processing
.
Comment pouvons-nous garder une trace du gâteau? Pour ce faire, définissez l'erreur Burned
dans l' action BakePie
.
module Kitchen module Errors class Burned < LunaPark::Errors::Processing; end end end
Et pendant la cuisson, vérifiez que notre tarte n'a pas grillé:
module Kitchen module Services class BakePie < LunaPark::Callable def call
Dans ce cas, le piège d'erreur fonctionnera et nous pourrons les traiter dans
.
Les erreurs non héritées du Processing
sont perçues comme des erreurs système et seront interceptées au niveau du serveur. Sauf indication contraire, l'utilisateur recevra 500 ServerError.
Utilisation pratique
1. Essayez de décrire tous les appels dans la méthode d'appel!
Vous ne devez pas implémenter chaque action dans une méthode distincte, cela rend le code plus gonflé. Vous devez regarder l'ensemble de la classe plusieurs fois pour comprendre comment cela fonctionne. Gâtez la recette de la cuisson d'une tarte:
module Service class CookingPieWithabbage < LunaPark::Interactors::Sequence def call! check_products_availability make_cabbage_filler make_pie bake end private def check_products_availability Services::CheckProductsAvailability.call list: ingredients end
Utilisez l'appel Ă l'action directement en classe. Du point de vue du rubis, cette approche peut sembler inhabituelle, elle semble donc plus lisible:
class DrivingStart < LunaPark::Interactors::Sequence def call! Service::CheckEngine.call Service::StartUpTheIgnition.call car, with: key Service::ChangeGear.call car.gear_box, to: :drive Service::StepOnTheGas.call car.pedals[:right] end end
2. Si possible, utilisez la méthode de classe d'appel
3. Ne créez pas d' objets fonctionnels pour taper le code, regardez la situation
Couche de service

L' Interacteur, comme nous l'avons dit, décrit la logique métier au plus haut niveau. La couche service ( couche service) révèle déjà les détails de la mise en œuvre des exigences fonctionnelles. Si nous parlons de faire une tarte, alors au niveau de l'interacteur, nous disons simplement «pétrir la pâte», sans entrer dans les détails sur la façon de la pétrir. Le processus de pétrissage est décrit au niveau du service . Revenons à la source originale, le grand livre bleu :
Dans le domaine appliqué, il existe des opérations qui ne trouvent pas de place naturelle dans un objet de type Entité ou Objet de valeur. Ce ne sont pas intrinsèquement des objets, mais des activités. Mais comme la base de notre paradigme de modélisation est l'approche objet, nous allons essayer de les transformer en objets.
À ce stade, il est facile de faire une erreur commune: abandonner la tentative de placer l'opération dans un objet approprié pour lui, et ainsi arriver à la programmation procédurale. Mais si vous placez avec force une opération dans un objet avec une définition qui lui est étrangère, cela fera perdre à l'objet lui-même sa pureté, ce qui le rendra plus difficile à comprendre et à refactoriser. Si vous implémentez beaucoup d'opérations complexes dans un objet simple, cela peut devenir incompréhensible quoi, ce que vous faites n'est pas clair quoi. De telles opérations impliquent souvent d'autres objets du domaine et la coordination entre eux est effectuée pour effectuer une tâche conjointe. Une responsabilité supplémentaire crée des chaînes de dépendance entre les objets, mélangeant des concepts qui pourraient être considérés indépendamment.
Lorsque vous choisissez un emplacement pour la mise en œuvre d'une fonctionnalité, utilisez toujours le bon sens. Votre tâche consiste à rendre le modèle plus expressif. Regardons un exemple, "Nous devons couper du bois":
module Entities class Wood def chop
Cette méthode sera une erreur. Le bois de chauffage ne se coupera pas, nous avons besoin d'une hache:
module Entities class Axe def chop(sacrifice)
Si nous utilisons un modèle économique simplifié, ce sera suffisant. Mais si le processus doit être modélisé plus en détail, nous avons besoin d'une personne qui coupera ces bois de chauffage, et peut-être d'un journal qui servira de support au processus.
module Entities class Human def chop_firewood(wood, axe, chock)
Comme vous l'avez probablement déjà deviné, ce n'est pas une bonne idée. Nous ne sommes pas tous engagés dans la coupe du bois, ce n'est pas le devoir direct d'une personne. Nous voyons souvent à quel point les modèles de Ruby on Rails sont surchargés, qui contiennent une logique similaire: obtenir des remises, ajouter des marchandises au panier, retirer de l'argent au solde. Cette logique ne s'applique pas à l'entité, mais au processus dans lequel cette entité est impliquée.
module Services class ChopFirewood
Après avoir déterminé quelle logique nous stockons dans les Services, nous allons essayer de mettre en œuvre l'un d'entre eux. Le plus souvent, les services sont implémentés via des méthodes ou des objets fonctionnels.
Objets fonctionnels
Un objet fonctionnel remplit une exigence fonctionnelle. Dans sa forme la plus primitive, un objet fonctionnel a une seule méthode publique - call
.
module Serivices class Sum def initialize(x, y) @x = x @y = y end def call x + y end def self.call(x,y) new(x,y).call end private attr_reader :x, :y end end
De tels objets présentent plusieurs avantages: ils sont concis, ils sont très simples à tester. Il y a un inconvénient, ces objets peuvent s'avérer être un grand nombre. Il existe plusieurs façons de regrouper des objets similaires; dans une partie de nos projets, nous les divisons par type:
- Objet de service (Service) - un objet, crée un nouvel objet;
- Commande (Commande) - modifie l'objet actuel;
- Guardian (Guard) - renvoie une erreur en cas de problème.
Objet de service
Dans notre implémentation, Service - implémente une exigence fonctionnelle et renvoie toujours une valeur.
module KorovaMilkBar module Services class FindMilk < LunaPark::Callable GLASS_SIZE = Values::Unit.wrap '200g' def initialize(fridge:) @fridge = fridge end def call fridge.shelfs.find { |shelf| shelf.has?(GLASS_SIZE, of: :milk) } end private attr_reader :fridge end end end FindMilk.call(fridge: the_red_one)
Commande
Dans notre implémentation, Command - effectue une action , modifie l'objet, si true renvoie true. En fait, l' équipe ne crée pas d'objet, mais modifie un objet existant.
module KorovaMilkBar module Commands class FillGlass < LunaPark::Callable def initialize(glass, with:) @glass = glass @content = with end def call glass << content true end private attr_reader :fridge end end end glass = Glass.empty milk = Milk.new(200, :gr) glass.empty?
Gardien (Garde)
Le gardien effectue une vérification logique et en cas d'échec donne une erreur de traitement. Ce type d'objet n'affecte en rien la direction principale , mais nous fait basculer vers la direction alternative en cas de problème.
Lors du service du lait, il est important de s'assurer qu'il est frais:
module KorovaMilkBar module Guards class IsFresh < LunaPark::Callable def initialize(product) @products = products end def call products.each do |product| raise Errors::Rotten, "
Il peut être utile de séparer les objets fonctionnels par type. Vous pouvez ajouter le vôtre, par exemple, Builder - crée un objet basé sur des paramètres.
Arrangements
- La méthode d'
call
est la seule méthode publique obligatoire. - La méthode
initialize
est la seule méthode publique facultative. - Le reste des méthodes doit être privé.
- Les erreurs logiques doivent être héritées de la
LunaPark::Errors::Processing
.
Gestion des erreurs
Il existe 2 types d'erreurs qui peuvent se produire pendant le fonctionnement d'une action .
Erreurs d'exécution
De telles erreurs peuvent survenir Ă la suite d'une violation de la logique de traitement.
Par exemple:
- lors de la création d'un e-mail utilisateur est réservé;
- quand vous essayez de boire du lait, c'est fini;
- un autre microservice a rejeté l'action (pour une raison logique, et non parce que le service n'est pas disponible).
Selon toute vraisemblance, l'utilisateur voudra connaître ces erreurs. Ce sont aussi probablement les erreurs
que nous pouvons prévoir.
Ces erreurs doivent être héritées de LunaPark::Errors::Processing
Erreurs système
Erreurs survenues à la suite d'un plantage du système.
Par exemple:
- la base de données ne fonctionne pas;
- quelque chose divisé par zéro.
Selon toute vraisemblance, nous ne pouvons pas prévoir ces erreurs et ne pouvons rien dire à l'utilisateur, sauf que tout est très mauvais, et envoyer aux développeurs un rapport appelant à l'action. Ces erreurs doivent être héritées de SystemError
Il existe également des erreurs de validation , dont nous discuterons plus en détail dans le prochain article.
Utilisation pratique
1. Utilisez des variables pour augmenter la lisibilité
module Fishing
2. Passez des objets, pas des paramètres
Essayez de simplifier l'initialiseur si le traitement des paramètres n'est pas son objectif.
Passez des objets, pas des paramètres.
module Service
3. Utilisez le nom Actions - le verbe de l'action et l'objet d'influence.
4. Si possible, utilisez la méthode de classe d'appel
Habituellement, une instance de la classe Actions , rarement utilisée autre que l'écriture pour effectuer un appel.
5. La gestion des erreurs n'est pas une tâche de service
Modules
Jusqu'à ce moment, nous avons considéré l'implémentation de la couche Service comme un ensemble d'objets fonctionnels. Mais nous pouvons facilement placer des méthodes sur cette couche:
module Services def sum(a, b) a + b end end
Un autre problème auquel nous sommes confrontés est un grand nombre d'installations de service. Au lieu du «modèle fat rails», que nous avons mis de côté, nous obtenons le «dossier fat services». Il existe plusieurs façons d'organiser la structure pour réduire l'ampleur de la tragédie. Eric Evans résout ce problème en combinant un certain nombre de fonctions en une seule classe. Imaginez que nous devons modéliser les processus commerciaux de la nounou, Arina Rodionovna, elle peut nourrir Pouchkine et le coucher:
class NoonService def initialize(arina_radionovna, pushkin)
Cette approche est plus correcte du point de vue de la POO. Mais nous suggérons de l'abandonner, au moins dans les premières étapes. Les programmeurs peu expérimentés commencent à écrire beaucoup de code dans cette classe, ce qui conduit finalement à une connectivité accrue. Au lieu de cela, vous pouvez utiliser le module, représentant l'activité comme une abstraction:
module Services module Noon class ToFeed def call!
Lors de la division en modules, un faible couplage externe (faible couplage) doit être observé avec une forte cohésion interne (forte cohésion), mais nous utilisons des modules tels que les services ou les interacteurs, cela va également à l'encontre des idées d'architecture pure. Il s'agit d'un choix conscient qui facilite la perception. Par le nom du fichier, nous comprenons quel modèle de conception implémente telle ou telle classe, si cela est évident pour un programmeur expérimenté, ce n'est pas toujours le cas pour un débutant. Une fois que votre équipe est prête, jetez cet excès.
Pour citer un autre petit extrait du grand livre bleu:
Choisissez des modules qui racontent l'histoire du système et contiennent des ensembles cohérents de concepts. Cela conduit souvent à une faible dépendance des modules les uns par rapport aux autres. Mais si ce n'est pas le cas, trouvez un moyen de changer le modèle de manière à séparer les concepts les uns des autres, ou recherchez le concept qui manquait dans le modèle, qui pourrait devenir la base du module et ainsi rapprocher les éléments du modèle de manière naturelle et significative. Atteindre une faible dépendance des modules les uns par rapport aux autres dans le sens où les concepts des différents modules peuvent être analysés et perçus indépendamment les uns des autres. Affinez le modèle jusqu'à ce que des limites naturelles y apparaissent conformément aux concepts de haut niveau du domaine concerné, et le code correspondant n'est pas divisé en conséquence.
Donnez aux modules les noms qui seront inclus dans la LANGUE UNIFIÉE. Les MODULES eux-mêmes et leurs noms doivent refléter la connaissance et la compréhension du sujet.
Le sujet des modules est vaste et intéressant, mais dans son intégralité va clairement au-delà du sujet de cet article. La prochaine fois, nous parlerons avec vous des référentiels et adaptateurs . Nous avons ouvert une chaîne de télégrammes confortable où nous aimerions partager des documents sur le thème du DDD. Nous apprécions vos questions et commentaires.