Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean Architecture in Ruby - Tropical Ruby 2015

01684ebe74590fa2d4c6f37e7a47c043?s=47 bezelga
March 07, 2015

Clean Architecture in Ruby - Tropical Ruby 2015

What, how and when to apply Clean Architecture in Ruby. Using Rails as a delivery mechanism.

I wrote an article about it here: https://medium.com/@fbzga/clean-architecture-in-ruby-7eb3cd0fc145

01684ebe74590fa2d4c6f37e7a47c043?s=128

bezelga

March 07, 2015
Tweet

Transcript

  1. CLEAN ARCHITECTURE IN RUBY Fabiano Beselga @fbzga

  2. None
  3. CHAPTERS 1. What is Clean Architecture? 2. How to use

    it with Rails? 3. When to use it?
  4. CHAPTER I What is Clean Architecture?

  5. the PROBLEM

  6. None
  7. your top level structure is screaming the web FRAMEWORK

  8. The web is a delivery mechanism

  9. yet it DOMINATES your code

  10. image you’ve just joined a PROJECT

  11. None
  12. what makes a GOOD architecture?

  13. None
  14. Architecture is about Intent

  15. Ivar Jacobson

  16. BUE • boundaries • USE CASES • entities

  17. application specific business rules USE CASES (aka Interactors)

  18. ENTITIES application independent business rules

  19. communication interfaces Boundaries

  20. diagrams: Robert C Martin

  21. None
  22. None
  23. None
  24. None
  25. None
  26. What about MVC?

  27. MVC as a Web architecture

  28. What about the database?

  29. None
  30. The database is a detail

  31. Isolate it!

  32. images: Robert C Martin

  33. CHAPTER II HOW to use it with Rails?

  34. Let’s create an accounting app called Lannister

  35. None
  36. Lannisters and Starks have accounts at the Iron Bank of

    Braavos
  37. Sometimes they want to transfer money between their accounts

  38. trades debit: -1000 credit: +1000

  39. None
  40. 7 steps

  41. bundle gem Lannister 1. Create project

  42. RDD (README driven development) 1. Create project 2. Define API

  43. Usage: Transfer money Lannister. transfer_money(source_account_id: 1, destination_account_id: 2, amount: 10_000)

  44. BDD 1. Create project 2. Define API 3. Define behavior

  45. None
  46. the ENTRY POINT 1. Create project 2. Define API 3.

    Define behavior 4. Entry point
  47. module Lannister class << self delegate :transfer_money, to: UseCases::TransferMoney end

    end
  48. it would be better to run this use case inside

    a transaction
  49. require 'caze' module Lannister include Caze has_use_case :transfer_money, UseCases::TransferMoney, transactional:

    true end github.com/magnetis/caze
  50. the USE CASE 1. Create project 2. Define API 3.

    Define behavior 4. Entry point 5. Use case
  51. module Lannister module UseCases class TransferMoney def transfer return false

    if get_balance(account_id: source_account_id) < amount trade_repo.persist Entities::Trade.new(account_id: source_account_id, amount: - amount) trade_repo.persist Entities::Trade.new(account_id: destination_account_id, amount: amount) end end end end
  52. the TRADE Entity 1. Create project 2. Define API 3.

    Define behavior 4. Entry point 5. Use case 6. Entity
  53. require 'active_model' module Lannister module Entities class Trade include ActiveModel::Model

    attr_accessor :id, :account_id, :amount, :date validates_presence_of :account_id, :amount, :date end end end
  54. the REPOSITORY 1. Create project 2. Define DSL 3. Define

    behavior 4. Entry point 5. Use case 6. Entity 7. Repository
  55. let’s isolate DATA within a RAILS ENGINE

  56. rails plugin new lannister_data --mountable

  57. rails g model account name rails g model trade account:references

    amount:decimal date:date
  58. engine/ app/ models/ repositories/

  59. module LannisterData class TradeRepo def self.persist(entity) row = Trade.create!(account_id: entity.account_id,

    amount: entity.amount) entity.id = row.id entity end end end
  60. Lannister data Lannister behavior Let’s connect them

  61. lannister.gemspec Gem::Specification.new do |spec| spec.add_dependency 'lannister_data' spec.add_development_dependency ‘sqlite3' end

  62. require 'lannister_data/engine' module Lannister def self.trade_repo LannisterData::TradeRepo end end

  63. spec_helper.rb ActiveRecord::Base.establish_connection(database: ":memory:", adapter: “sqlite3") ActiveRecord::Migrator.migrate(LannisterData::Engine.paths['db/migrate'].existent)

  64. None
  65. 1. Create project 2. Define API 3. Define behavior 4.

    Entry point 5. Use case 6. Entity 7. Repository DONE!
  66. Lannister data Lannister behavior

  67. let’s expose it to the web with a Rails APP

  68. rails new lannister_web

  69. Gemfile gem 'lannister'

  70. rake lannister_data:install:migrations rake db:migrate

  71. let’s call it on a RAILS CONTROLLER

  72. class TransferMoneyController < ApplicationController before_action :load_balance def create if Lannister.transfer_money(source_account_id:

    source_account_id, destination_account_id: destination_account_id, amount: amount) redirect_to new_transfer_money_path else flash[:error] = 'Not enough money on the source account' render :new end end private def load_balance @balance = Lannister.get_balance(account_id: current_account_id) end end
  73. what just happened?

  74. the gem represents WHAT THE SYSTEM Does (behavior)

  75. None
  76. the engine represents WHAT THE SYSTEM IS (data)

  77. Lannister data Lannister Rails CORE delivery mechanism behavior stable API

  78. Lannister Web Repository UI DB Controllers Views

  79. github.com/bezelga/lannister github.com/bezelga/lannister_data github.com/bezelga/lannister_web

  80. what about real life?

  81. investment Advisor

  82. magnetis.com.br

  83. 3 years old codebase

  84. experimenting with clean architecture for a year

  85. None
  86. the first seeds magnetis_core

  87. a gem for each DOMAIN CONCEPT aka modularization

  88. rails-app/ engines/ lannister_data … gems/ lannister …

  89. we have a gem for portfolio recommendation Advisor

  90. Advisor.recommend_portfolio_for(params)

  91. None
  92. we have a gem for accounting Accountant

  93. None
  94. a gem for ENTITIES

  95. we also use vanilla Rails

  96. CHAPTER III when to use it?

  97. 3 Heuristics

  98. #1 distance from the view

  99. the domain logic is far from the view Clean architecture

    #1
  100. the domain logic is close to the view Rails way

    #1
  101. #2 your company’s context

  102. core business your product #2 rails way clean architecture

  103. #3 TIMING

  104. #3 image: Kent Beck

  105. Lessons learned

  106. NOT so good • more files to handle in your

    cognitive memory • over engineering when the domain logic is close to the view • getting out of the rails way may hurt in the beginning
  107. • delay decisions until the last responsible moment • makes

    clear app’s intent (not how it’s built) • business rules isolated from the web, framework and database • consequence: tests running faster the cool STUFF
  108. I am not saying that EVERYONE should be using this

    approach
  109. tasting different approaches is nice

  110. we are hiring fabiano@magnetis.com.br

  111. @fbzga