Правильний спосіб кодування DCI в Ruby

Перекладено з http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby

Багато предмети, знайдені в співтоваристві Ruby в основному спрощувати використання DCI. Ці статті, у тому числі мій власний, показати, як DCI вводить ролей в об’єкти під час виконання, суть архітектури DCI. Багато хто вважає повідомлень DCI наступним чином:

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

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

Є кілька недоліків з більш спрощених прикладів, як це. По-перше, він читає “це, як це зробити DCI”. DCI набагато більше, ніж просто проходять об’єктів. По-друге, він підкреслює #extend як перейти до допомоги додавання методів до об’єктів під час виконання. У цій статті, я хотів би особливо звернутися до колишнього питання: DCI за рамки просто проходять об’єктів. Стежити за пост буде містити порівняння методів, щоб надати ролей в об’єкти, використовуючи #extend і в іншому випадку.

DCI (Дані-контекст-взаємодії)

Як зазначалося раніше, DCI це набагато більше, ніж просто розширення об’єктів під час виконання. Йдеться про захоплення ментальну модель кінцевого користувача та реконструкції, що в підтримуваному коду. Це за межами → підходу, схожий на BDD, де ми розглядаємо взаємодію користувача перший і другий моделі даних. Зовнішній → підходу є однією з причин, я люблю архітектуру; він добре вписується в стиль BDD, які в подальшому сприяє проверяемость.

Важливо знати про DCI, що це більше, ніж просто код. Йдеться процесі, і люди. Вона починається з принципів, що лежать в Agile і Lean і розширює ті в коді. Реальна перевага після DCI, що він грає добре з Agile і Lean. Мова йде про ремонтопридатності коду, реагувати на зміни, і розв’язки, що система робить (це функціональність) від того, що система (це модель даних).

Я візьму поведінку керованої підхід до реалізації DCI в додаток Rails, починаючи з взаємодії і перехід до моделі даних. Здебільшого, я збираюся написати код спочатку тоді тест. Звичайно, коли у вас є тверде розуміння компонентів за DCI, ви можете написати тести в першу чергу. Я просто не відчуваю, тест-перше, це відмінний спосіб пояснити поняття.

Історії користувачів

Користувальницькі історії є важливою ознакою DCI, хоча і не відрізняється від архітектури. Вони відправною точкою визначення того, що система робить. Один з красунь, починаючи з користувальницьких історій в тому, що він добре вписується в жвавий процесу. Як правило, ми будемо приділяти історію, яка визначає нашу функцію кінцевого користувача. Спрощена історія може виглядати наступним чином:

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

На даний момент, у нас є загальне уявлення про функції ми будемо реалізує.

Крім: більш формальне виконання DCI потрібно перетворюючи історію користувача у разі використання. Прецедент потім надати нам додаткові роз’яснення на вхід, вихід, мотивація, ролі, і т.д.

Написати деякі тести

Ми повинні мати достатньо в цьому пункті, щоб написати приймальні випробування для цієї функції. Давайте використовувати RSpec і Capybara:

spec/integration/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

У дусі BDD, ми почали визначити, як наш домен модель (наші дані) буде виглядати. Ми знаємо, що книга буде містити атрибут заголовка. У дусі DCI, ми визначили контекст, для якого цей випадок використання вводить і актори, які грають ключові ролі. Контекст додає книгу в корзину. Актор ми визначили це користувачів.

Реально, ми хотіли б додати більше тестів для подальшого покриття цю функцію, але вище нас влаштовує і в даний час.

В “Ролі”

Актори грають ролі. Для цієї особливості, ми дійсно тільки один актор, користувач. Користувач грає роль клієнта, дивлячись, щоб додати елемент в їх кошик. Ролі описують алгоритми, використовувані для визначення того, що система робить.

Давайте код його:

app/roles/customer.rb

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

Створення нашої ролі клієнтів допоміг дражнити більше інформації про нашу моделі даних, користувач. Тепер ми знаємо, нам знадобиться метод #cart про яких-небудь об’єктів даних, які грає роль клієнтів.

Роль Клієнт визначено вище, не піддавайте багато про те, що #cart є. Один дизайнерське рішення я зробив заздалегідь, для простоти, це припустити, віз буде зберігатися в базі даних, а не на sesssion. Метод #cart визначений в будь-якому актор грає роль клієнтів не повинно бути продумана реалізація корзину. Я просто припустити, простий асоціації.

Ролі також приємно грати з поліморфізмом. Роль клієнтів можуть бути відтворені будь-яким об’єктом, який реагує на методу #cart. Сама роль ніколи не знає, який тип об’єкта буде збільшити, залишивши, що рішення за підсумками контексті.

Написати деякі тести

Давайте стрибати назад в режим тестування і написати кілька тестів навколо нашого новоствореного роль.

spec/roles/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

Наведений вище код тест також висловлює, як ми будемо використовувати цю роль, клієнт, в даному контексті, додавши книгу в корзину. Це робить сегм шлях справді писати контексту мертвих простий.

«Контекст»

У DCI, контекст навколишнього середовища, для яких об’єкти даних виконати свої ролі. Існує завжди принаймні один контекст для кожного оповідання одного користувача. Залежно від складності сюжету користувача, може бути більше, ніж один контекст, можливо, вимагаючи історія брейк-вниз. Мета контексті є підключення ролей (що робить система) для об’єктів даних (те, що система).

На даний момент, ми знаємо, роль, яку ми будемо використовувати, клієнт, і ми маємо сильну ідею про об’єкт даних ми будемо збільшення, користувач.

Давайте код його:

app/contexts/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

Оновлення: здійснення Джима Coplien контекстів використовує AddToCartContext # виконати, як в контекстному тригера. Для підтримки Ruby, ідіоми, путь і лямбда, приклади були змінені, щоб використовувати AddToCartContext # виклик.

Там це кілька ключових моментів, щоб відзначити:

  • Контекст визначається як клас. Акт екземпляра класу і виклику Це #call метод відомий як запуск.
  • Маючи метод класу AddToCartContext.call просто зручний метод, щоб допомогти у запуску.
  • Суть DCI в @user.extend замовника. Об’єкти збільшення даних з ролями спеціальних те, що дозволяє для сильного розв’язки. Там ви мільйон способів ін’єкційні ролей в об’єкти, що є одним #extend. У Наступні статті, я буду розглядати інші способи, в яких це може бути досягнуто.
  • Проходячи користувачів і книжкові об’єкти в контексті може привести до колізії імен методів на роль. Щоб полегшити це, це було б прийнятно, щоб пройти user_id і book_id в контексті і дозволяють контекст для створення екземпляра зв’язані об’єкти.
  • Контекст повинні піддавати акторів, для яких вона дозволяє. У цьому випадку, attr_reader використовується, щоб виставітьuser іbook. book не актор в цьому контексті, однак вона схильна для повноти.
  • Більшістьпримітка вміло: Ви повинні рідко доводиться (неймовірно) #unextend ролі з об’єкта. Об’єкт даних, як правило, грають тільки одну роль, у той час в даному контексті. Там повинен бути тільки один контекст у разі використання (акцент: за використання випадку, користувач не може історія). Таким чином, ми повинні рідко потрібно видалити функціональність або ввести конфліктів імен. У DCI, це прийнятно, щоб надати кілька ролей в об’єкт у даному контексті. Таким чином, проблема конфліктів імен ще перебуває, але повинні відбуватися рідко.

Написати деякі тести

Я взагалі не великий прихильник глузливий і гасячи але я думаю, що це доцільно у випадку контекстів, тому що ми вже перевірили виконання коду в нашу роль специфікації. На даний момент ми просто тестування інтеграції.

spec/contexts/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

Основна мета вище код, щоб переконатися, що ми називаємо метод #add_to_cart з правильними аргументами. Ми робимо це, встановивши очікування, що користувач Актор в AddToCartContext повинні це #add_to_cart метод, званий з книгою в якості аргументу.

Там не набагато більше, щоб DCI. Ми покрили взаємодія між об’єктами і контексті, для яких вони взаємодіють. Важливо код вже написаний. Єдине, що залишилося це німі даних.

“Дані”

Дані повинні бути невеликі. Хороший правило полягає в тому, щоб ніколи не визначити методи ваших моделей. Це не завжди так. Краще покласти: “інтерфейси об’єктів даних є простими і мінімальними: достатньо, щоб захопити властивостей домену, але без операцій, які є унікальними для будь-якої конкретної ситуації”. Дані повинні дійсно складатися тільки з методів збереження рівня, ніколи, як використовується збережених даних. Давайте подивимося на модель Book, для яких ми вже дражнили з основних атрибутів.

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

Немає методи. Всього визначення класу рівня стійкості, асоціації та перевірки даних. Способи, в яких використовуються Книга не повинна бути проблемою моделі Book. Ми могли б написати кілька тестів навколо моделі, і ми, ймовірно, слід. Тестування валідації та асоціації є досить стандартним, і я не буду розглядати їх тут.

Зберігайте ваші дані німий.

Вбудовується в Rails

Там не багато, щоб сказати про установку вище код в Rails. Простіше кажучи, ми не приведемо наш контекст в контролері.

app/controllers/book_controller.rb

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

Ось схема, що ілюструє, як DCI компліменти Rails MVC. Контекст стає шлюзом між користувача інтерфейсом і моделі даних.

Що ми зробили

Наступні показники можуть гарантувати його власної статті, але я хочу коротко розглянемо деякі з переваг структурування коду з DCI.

  • Ми високо відокремити функціональні системи від того, як дані насправді зберігається. Це дає нам додаткову перевагу стиснення і легкої поліморфізму.
  • Ми створили читаний код. Це легко міркувати про коді як по іменам файлів і алгоритмів всередині. Це все дуже добре організовано. Див лещата дядька Боба про фото рівня читабельності.
  • Наша модель даних, що система може залишатися стабільним, а ми прогресувати і реорганізовувати ролей, що робить система.
  • Ми прийшли ближче до представляють ментальну модель кінцевого користувача. Це основна мета MVC, те, що було спотворено в протягом довгого часу.

Так, ми додаємо ще один шар складності. Ми повинні стежити за контекстів і ролей на вершині нашої традиційної MVC. Контексти, зокрема, проявляють більше коду. Ми ввести трохи більше накладні витрати. Тим не менш, з цим накладних витрат йде великій мірі процвітання. Як розробник або команда розробників, це ваш descretion на ці переваги вирішити ваші ділові та інженерні недуг.

Висновки

Проблеми з декомпрессионной хвороби, а також. По-перше, він вимагає великого парадигми. Вона розроблена, щоб хвалити MVC (Model-View-Controller), так що добре вписується в Rails, але це вимагає, щоб ви рухатися весь код поза контролера і моделі. Як ми всі знаємо, співтовариство Rails має фетиш для здачі код моделі і контролери. Зрушення парадигми є великою, то, що потребують великого рефакторинг для деяких додатків. Тим не менш, DCI, ймовірно, може бути перероблений в на індивідуальній основі випадку, що дозволяє додаткам поступово перейти від «жирних моделей, худий контролерів”, щоб DCI. По-друге, можливо, несе зниження продуктивності, у зв’язку з тим, що об’єкти розширеної спеціальної.

Основна перевага DCI стосовно співтовариства Ruby є те, що він забезпечує структуру для для обговорення в супроводі код. Там було багато недавнього обговорення в дусі “жиру моделей, худий контролери погано”; не ставте код у ваш контролер або моделі, покласти її в іншому місці.” Проблема ми не вистачає вказівки на якому наш код повинен жити і як вона повинна бути структурована. Ми не хочемо, в моделі, ми не хочемо його в контролері, і ми, звичайно, не хочу його представлення. Для більшості, дотримуючись цих вимог призводить до плутанини, над техніки, і загальна відсутність узгодженості. DCI дає нам план, щоб розірвати рейки форми і створити супроводі, перевіряються, розв’язкою код.

Крім: Там був інший роботи в цій області. Авді Грімм (Avdi Grimm) має феноменальною книгу під назвою Об’єкти на рейках (Objects on Rails), який пропонує альтернативний рішення.

Щасливий архітектури!





Most popular articles:


  • Photo-news blog

  • Keyword-pedia