Traduction 1001-soldes.com ©2017 Tous droits réservés | Texte original : http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby

La bonne façon de coder DCI en Ruby

De nombreux articles trouvés dans la communauté Ruby en grande partie l'utilisation de trop simplifier DCI. Articles pour ces derniers , incluant le mien , le point culminant de la façon dont la DCI injecte les rôles des objets À l'exécution , l'essence de l'architecture de la DCI. De nombreux postes considèrent DCI de la manière suivante:

 class User; end # Data
module Runner # Role
  def run
    ...
  end
end

user = User.new # Context
user.extend Runner
user.run

Il y a quelques défauts avec des exemples oversimpilfied comme celui-ci. Tout d'abord, il lit «c'est comment faire DCI ». DCI est beaucoup plus qu'un élargissement des objets. Second, comme l'IT #extend les moments forts de la! Go à moyen d'ajouter des méthodes d'objets à l'exécution . Dans cet article, je voudrais aborder spécifiquement la première question: DCI au-delà des objets s'étendant. Signalera suivi A contient une comparaison des techniques d'injecter dans des objets rôles à l' aide #extend et autrement .

DCI (données-contexte d'interaction)

Comme indiqué précédemment, DCI est bien plus qu'une simple extension des objets à l'exécution. Il est sur la capture modèle mental de l'utilisateur final et la reconstruction que dans le code maintenable. Il est un extérieur → approche, semblable à BDD, où nous considérons l'interaction utilisateur premier et le modèle de données seconde. Le → de l'extérieur dans l'approche est l'une des raisons pour lesquelles j'aime l'architecture; il intègre bien dans un style BDD, qui favorise davantage la testabilité.

La chose importante à savoir au sujet de DCI est qu'il est plus que juste le code. Il est sur le processus et les gens. Il commence par les principes derrière et Lean et Agile étend les en code. Le véritable avantage de suivre DCI est qu'il joue bien avec Lean et Agile. Il est sur le code maintenabilité, répondant à changer, et le découplage ce que le système fait (ses fonctionnalités) de ce que le système est (il est modèle de données).

Je vais prendre une approche axée sur le comportement à la mise en œuvre DCI dans une application Rails, en commençant par les interactions et passer au modèle de données. Pour la plupart, je vais écrire un code d'abord, puis test. Bien sûr, une fois que vous avez une solide compréhension des composants derrière DCI, vous pouvez écrire des tests en premier. Je ne me sens pas seulement des tests en premier est un excellent moyen d'expliquer les concepts.

Histoires de l'utilisateur

Histoires d'utilisateurs sont une caractéristique importante de DCI mais pas distinct à l'architecture. Ils sont le point de départ de définir ce que fait le système. L'une des beautés de commencer par des histoires de l'utilisateur est qu'il intègre bien dans un processus Agile. En règle générale, nous allons donné une histoire qui définit notre fonction de l'utilisateur final. Une histoire simplifiée pourrait ressembler à ce qui suit:

 "As a user, I want to add a book to my cart."

À ce stade, nous avons une idée générale de la fonction que nous allons mettre en œuvre.

En plus: Une mise en œuvre plus formelle de DCI nécessiterait transformer une histoire d'utilisateur dans un cas d'utilisation . Emploierait un cas de ce qui était alors la nous fournir plus de précisions sur l'entrée de la , la production, la motivation, les rôles, etc.

Ecrire certains tests,

Nous devrions avoir assez à ce moment d'écrire un test d'acceptation pour cette fonction. S pour utiliser Soit la RSpec et Capybara :

spec / intégration / add_to_cart_spec.rb

 describe 'as a user' do
  it 'has a link to add the book to my cart' do
    @book = Book.new(:title => 'Lean Architecture')
    visit book_path(@book)
    page.should have_link('Add To Cart')
  end
end

Dans l'esprit de BDD, nous avons commencé à identifier la façon dont notre modèle de domaine (nos données) se penchera. Ce livre a le savoir car nous de contenir un titre l'attribut . Dans l'esprit de DCI, nous avons identifié le contexte pour lequel ce cas d'utilisation et promulgue les acteurs qui jouent des rôles clés. Le contexte est l'ajout d'un livre à votre panier. L'acteur que nous avons identifié est l'utilisateur.

De façon réaliste, nous ajouterons d'autres tests pour couvrir davantage cette fonction, mais les costumes ci-dessus nous bien pour l'instant.

Les « rôles »

Les acteurs jouent un rôle. Pour que cette fonction spécifique, nous avons vraiment un seul acteur, l'utilisateur. L'utilisateur joue le rôle d'un client à la recherche d'ajouter un article à leur panier. Le décrivent les algorithmes les rôles utilisés à définir ce que le système ne .

Let it up code général:

app / rôles / customer.rb

 module Customer
  def add_to_cart(book)
    self.cart << book
  end
end

Créer notre rôle client a aidé taquinent plus d'informations sur notre modèle de données, l'utilisateur. Nous savons maintenant que nous aurons besoin d' une #cart méthode sur tous les objets de données qui joue le rôle de client .

Le rôle défini par le client des précède ne pas-expose- beaucoup sur ce qui est #cart . Une décision de conception que j'ai fait à l'avance, par souci de simplicité, est d'assumer le panier sera stocké dans la base de données au lieu de la sesssion. méthode définie #cart de la sur le tout l'acteur jouant le rôle Si le client ne l'être une partie de la mise en œuvre élaborée d'un chariot . Je suppose simplement une simple association.

Les rôles jouent aussi bien avec le polymorphisme. Pourrait le rôle de client du BE joué par le tout objet qui répond à la méthode de la #cart . Le rôle lui-même ne sait jamais quel type d'objet il augmentera, laissant cette décision jusqu'à le contexte.

Ecrire certains tests,

Débutons revenir en mode de test et écrire quelques tests autour de notre nouveau rôle.

spec / rôles / customer_spec.rb

 describe Customer do
  let(:user) { User.new }
  let(:book) { Book.new }

  before do
    user.extend Customer
  end

  describe '#add_to_cart' do
    it 'puts the book in the cart' do
      user.add_to_cart(book)
      user.cart.should include(book)
    end
  end
end

Le code de test ci-dessus exprime également la façon dont nous utiliserons ce rôle, le Client, dans un contexte donné, l'ajout d'un livre au panier. Cela rend le segway en train d'écrire le contexte mort simple.

Le « contexte »

En DCI, le contexte est l'environnement pour lequel des objets de données exécuter leurs rôles. Il y a toujours au moins un contexte pour chaque histoire d'utilisateur. En fonction de la complexité de l'histoire de l'utilisateur, il peut y avoir plus d'un contexte, ce qui nécessite peut-être une histoire rupture vers le bas. But du contexte de Le est la connexion à des rôles (ce que le système fait) aux objets de données (ce que le système est).

À ce stade, nous savons le rôle que nous allons utiliser, le client, et nous avons une idée forte sur l'objet de données, nous allons augmentons, l'utilisateur.

Let it up code général:

app / contextes / add_to_cart_context.rb

 class AddToCartContext
  attr_reader :user, :book

  def self.call(user, book)
    AddToCartContext.new(user, book).call
  end

  def initialize(user, book)
    @user, @book = user, book
    @user.extend Customer
  end

  def call
    @user.add_to_cart(@book)
  end
end

Mise à jour: la mise en œuvre de Jim Coplien Contextes utilise AddToCartContext # exécutent comme déclencheur de contexte. Pour soutenir idiomes Ruby, procs et lambdas, les exemples ont été modifiés pour utiliser AddToCartContext # appel.

Il y a quelques points clés à noter:

Ecrire certains tests,

Je suis généralement pas un grand partisan de moqueries et stubbing mais je pense qu'il est approprié dans le cas de Contextes parce que nous avons déjà testé le code en cours d'exécution dans nos spécifications de rôle. À ce stade, nous testons simplement l'intégration.

spec / contextes / add_to_cart_context_spec.rb

 describe AddToCartContext do
  let(:user) { User.new }
  let(:book) { Book.new }

  it 'adds the book to the users cart' do
    context = AddToCartContext.new(user, book)
    context.user.should_recieve(:add_to_cart).with(context.book)
    context.call
  end
end

De But principal de Le ci - dessus sous le code est à vous assurer que les <br> nous appelons la méthode avec le #add_to_cart corriger vos arguments . Nous faisons par la présente pour nous mettre l'attente que l'utilisateur l'acteur Dans le AddToCartContext devrait l' avoir est #add_to_cart méthode appelée avec un argument avec le livre .

Il n'y a pas beaucoup plus à DCI. Nous avons couvert l'interaction entre les objets et le contexte pour lequel ils interagissent. Le code importante a déjà été écrit. La seule chose qui reste est les données muettes.

Les « données »

Les données doivent être minces. Une bonne règle de base est de définir des méthodes jamais sur vos modèles. Ce n'est pas toujours le cas. Mieux mettre: « interfaces objet de données sont simples et minimes: juste assez pour capturer les propriétés du domaine, mais sans les opérations qui sont uniques à un scénario particulier » (Architecture Lean). Les données devraient consister en réalité que de la persistance des méthodes, jamais comment les données est utilisé persistant. Regardons le modèle du livre pour lequel nous avons déjà taquiné les attributs de base.

 class Book < ActiveRecord::Base
  validates :title, :presence => true
end

Aucune méthode. Juste au niveau des définitions de classe de persistance, d'association et la validation des données. La manière dont est utilisé livre ne devrait pas être une préoccupation du modèle livre. Nous pourrions écrire des tests autour du modèle, et nous devrions probablement. Test et associations est validations assez standard et je ne vais pas les couvrir ici.

Gardez vos données stupide.

Rails raccord dans

Il n'y a pas beaucoup à dire sur le montage du code ci-dessus dans Rails. Autrement dit, nous déclenchons notre contexte dans le contrôleur.

app / controllers / book_controller.rb

 class BookController < ApplicationController
  def add_to_cart
    AddToCartContext.call(current_user, Book.find(params[:id]))
  end
end

Voici un diagramme illustrant comment complimente DCI Rails MVC. Le contexte devient une passerelle entre l'interface utilisateur et le modèle de données.

MVC + DCI

Ce que nous avons accompli

Ce qui suit pourrait justifier son propre article, mais je veux regarder brièvement quelques-uns des avantages du code de structuration avec DCI.

Oui, nous ajoutons une autre couche de complexité. Nous devons garder une trace des Contextes et des rôles au-dessus de notre MVC traditionnelle. Contextes, en particulier, présentent plus de code. Nous avons introduit un peu plus frais généraux. Cependant, avec cette surcharge vient en grande partie de la prospérité. En tant que développeur ou une équipe de développeurs, il est votre descretion si ces avantages pourraient résoudre vos maux d'affaires et de l'ingénierie.

final Words

Les problèmes avec DCI existent également. Tout d'abord, il faut un grand changement de paradigme. Il est conçu pour compléter MVC (Model-View-Controller) il convient bien dans Rails mais il vous oblige à déplacer tout votre code en dehors du contrôleur et le modèle. Comme nous le savons tous, la communauté Rails a un fétiche pour mettre le code dans les modèles et les contrôleurs. Le changement de paradigme est grande, ce qui nécessiterait un grand refactoring pour certaines applications. Cependant, DCI pourrait probablement être refactorisé sur une base au cas par cas permettant des applications de transférer progressivement à partir de « modèles de graisse, les contrôleurs maigres » à DCI. Second, IT Carries potentiellement une dégradation des performances , de la raison du fait que les objets étendus sont ad hoc .

Le principal avantage de DCI par rapport à la communauté Ruby est qu'il fournit une structure pour laquelle pour discuter du code maintenable. Il y a eu beaucoup de discussions récemment dans la veine de « modèles de graisse, les contrôleurs maigres est mauvais», ne pas mettre le code dans votre contrôleur ou votre modèle, mettre ailleurs. » Le problème est que nous sommes des conseils pour manque où notre code doit vivre et comment il doit être structuré. Nous ne voulons pas dans le modèle, nous ne voulons pas dans le contrôleur, et nous ne voulons certainement pas dans la vue. Pour la plupart, le respect de ces exigences conduit à la confusion, overengineering, et un manque général de cohérence. DCI nous donne un plan pour briser le moule Rails et créer un code maintenable, testables, découplé.

En plus: Il y a eu d' autres travaux dans ce domaine. A une grimm Avdi un livre phenominal appelé les objets sur les rails les qui propose des solutions alternatives.

Bonne architecting!

Lectures complémentaires

DCI et ouvert / fermé Principe
DCI avec des raffinements Ruby
DCI Rôle Injection dans Ruby
Analyse comparative DCI en Ruby

Publié par Mike Pack sur 01/24/2012 à 12h58 PM