Slide 1

Slide 1 text

CLEAN ARCHITECTURE IN RUBY Fabiano Beselga @fbzga

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

CHAPTERS 1. What is Clean Architecture? 2. How to use it with Rails? 3. When to use it?

Slide 4

Slide 4 text

CHAPTER I What is Clean Architecture?

Slide 5

Slide 5 text

the PROBLEM

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

your top level structure is screaming the web FRAMEWORK

Slide 8

Slide 8 text

The web is a delivery mechanism

Slide 9

Slide 9 text

yet it DOMINATES your code

Slide 10

Slide 10 text

image you’ve just joined a PROJECT

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

what makes a GOOD architecture?

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Architecture is about Intent

Slide 15

Slide 15 text

Ivar Jacobson

Slide 16

Slide 16 text

BUE • boundaries • USE CASES • entities

Slide 17

Slide 17 text

application specific business rules USE CASES (aka Interactors)

Slide 18

Slide 18 text

ENTITIES application independent business rules

Slide 19

Slide 19 text

communication interfaces Boundaries

Slide 20

Slide 20 text

diagrams: Robert C Martin

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

What about MVC?

Slide 27

Slide 27 text

MVC as a Web architecture

Slide 28

Slide 28 text

What about the database?

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

The database is a detail

Slide 31

Slide 31 text

Isolate it!

Slide 32

Slide 32 text

images: Robert C Martin

Slide 33

Slide 33 text

CHAPTER II HOW to use it with Rails?

Slide 34

Slide 34 text

Let’s create an accounting app called Lannister

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Lannisters and Starks have accounts at the Iron Bank of Braavos

Slide 37

Slide 37 text

Sometimes they want to transfer money between their accounts

Slide 38

Slide 38 text

trades debit: -1000 credit: +1000

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

7 steps

Slide 41

Slide 41 text

bundle gem Lannister 1. Create project

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

the ENTRY POINT 1. Create project 2. Define API 3. Define behavior 4. Entry point

Slide 47

Slide 47 text

module Lannister class << self delegate :transfer_money, to: UseCases::TransferMoney end end

Slide 48

Slide 48 text

it would be better to run this use case inside a transaction

Slide 49

Slide 49 text

require 'caze' module Lannister include Caze has_use_case :transfer_money, UseCases::TransferMoney, transactional: true end github.com/magnetis/caze

Slide 50

Slide 50 text

the USE CASE 1. Create project 2. Define API 3. Define behavior 4. Entry point 5. Use case

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

the TRADE Entity 1. Create project 2. Define API 3. Define behavior 4. Entry point 5. Use case 6. Entity

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

the REPOSITORY 1. Create project 2. Define DSL 3. Define behavior 4. Entry point 5. Use case 6. Entity 7. Repository

Slide 55

Slide 55 text

let’s isolate DATA within a RAILS ENGINE

Slide 56

Slide 56 text

rails plugin new lannister_data --mountable

Slide 57

Slide 57 text

rails g model account name rails g model trade account:references amount:decimal date:date

Slide 58

Slide 58 text

engine/ app/ models/ repositories/

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Lannister data Lannister behavior Let’s connect them

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

1. Create project 2. Define API 3. Define behavior 4. Entry point 5. Use case 6. Entity 7. Repository DONE!

Slide 66

Slide 66 text

Lannister data Lannister behavior

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

rails new lannister_web

Slide 69

Slide 69 text

Gemfile gem 'lannister'

Slide 70

Slide 70 text

rake lannister_data:install:migrations rake db:migrate

Slide 71

Slide 71 text

let’s call it on a RAILS CONTROLLER

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

what just happened?

Slide 74

Slide 74 text

the gem represents WHAT THE SYSTEM Does (behavior)

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

the engine represents WHAT THE SYSTEM IS (data)

Slide 77

Slide 77 text

Lannister data Lannister Rails CORE delivery mechanism behavior stable API

Slide 78

Slide 78 text

Lannister Web Repository UI DB Controllers Views

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

what about real life?

Slide 81

Slide 81 text

investment Advisor

Slide 82

Slide 82 text

magnetis.com.br

Slide 83

Slide 83 text

3 years old codebase

Slide 84

Slide 84 text

experimenting with clean architecture for a year

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

the first seeds magnetis_core

Slide 87

Slide 87 text

a gem for each DOMAIN CONCEPT aka modularization

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

we have a gem for portfolio recommendation Advisor

Slide 90

Slide 90 text

Advisor.recommend_portfolio_for(params)

Slide 91

Slide 91 text

No content

Slide 92

Slide 92 text

we have a gem for accounting Accountant

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

a gem for ENTITIES

Slide 95

Slide 95 text

we also use vanilla Rails

Slide 96

Slide 96 text

CHAPTER III when to use it?

Slide 97

Slide 97 text

3 Heuristics

Slide 98

Slide 98 text

#1 distance from the view

Slide 99

Slide 99 text

the domain logic is far from the view Clean architecture #1

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

#2 your company’s context

Slide 102

Slide 102 text

core business your product #2 rails way clean architecture

Slide 103

Slide 103 text

#3 TIMING

Slide 104

Slide 104 text

#3 image: Kent Beck

Slide 105

Slide 105 text

Lessons learned

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

• 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

Slide 108

Slide 108 text

I am not saying that EVERYONE should be using this approach

Slide 109

Slide 109 text

tasting different approaches is nice

Slide 110

Slide 110 text

we are hiring [email protected]

Slide 111

Slide 111 text

@fbzga