Slide 1

Slide 1 text

Embrace multi- model thinking! Ivan Nemytchenko, @inem, 2019

Slide 2

Slide 2 text

• Freelancing • Exploring apps architecture • Writing book • Teaching at Goodprogrammer.ru • Gitlabbing • Travelling Ivan Nemytchenko Omsk → Belgrade @inem http://inem.at

Slide 3

Slide 3 text

Rubytrip! 40 days, 4 conferences, 8 countries - RubyConfBY Minsk - April 6 - RubyDay Verona - April 11 - RubyWine Kishinev - April 13 - RailsConf Minneapolis - April 30 - Saint P RubyConf - June 1-2

Slide 4

Slide 4 text

Rubytrip! 40 days, 4 conferences, 8 countries - RubyConfBY Minsk - April 6 - RubyDay Verona - April 11 - RubyWine Kishinev - April 13 - RailsConf Minneapolis - April 30 - Saint P RubyConf - June 1-2

Slide 5

Slide 5 text

1.System thinking 2.State 3.Layers of abstractions 4.Side-effects 5.Entry point pressure 6.Building blocks

Slide 6

Slide 6 text

1.System thinking 2.State 3.Layers of abstractions 4.Side-effects 5.Entry point pressure 6.Building blocks

Slide 7

Slide 7 text

System thinking Lifecycle Roles ISO/IEC/ IEEE 42010 Systems and software engineering — Architecture description coursera.org/learn/system-thinking

Slide 8

Slide 8 text

: Roles System thinking

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Architect role

Slide 21

Slide 21 text

Number of models 25 50 75+

Slide 22

Slide 22 text

Inception → Growth → Operation → (Decay) System thinking: Lifecycle

Slide 23

Slide 23 text

Design Stamina Hypothesis by Martin Fowler

Slide 24

Slide 24 text

Inception → Growth → Operation → (Decay)

Slide 25

Slide 25 text

Inception → Growth → Operation → (Decay)

Slide 26

Slide 26 text

Inception → Growth → Operation → (Decay)

Slide 27

Slide 27 text

Inception → Growth → Operation → (Decay)

Slide 28

Slide 28 text

State

Slide 29

Slide 29 text

1.Invited 2.Registered 6.Deleted 3.Banned 5.Expired 4.Promoted User

Slide 30

Slide 30 text

1.Invited 2.Registered 6.Deleted 3.Banned 5.Expired 4.Promoted User Invoice 1.Created 2.Sent 3.Delivered 4.Paid 5.Expired Task 1.Created 2.Assigned 3.Sheduled 4.Completed 5.Archived

Slide 31

Slide 31 text

The natural way to implement states

Slide 32

Slide 32 text

- if current_organisation_subscribed? %p You are currently subscribed, using the following payment method: %p== #{@org.card_brand}: **** **** **** #{@org.card_last4} %p== Expires #{@org.card_exp_month} / #{@org.card_exp_year} - if @org.last_charge %p== The last payment of $#{@org.last_charge.amount/100} was taken on #{@org.last_charge.created_at.strftime("%d %b % %p== The next payment of $100 is due to be taken on #{(@org.last_charge.created_at + 1.month).strftime("%d %b %Y")} = link_to "Update credit card", edit_subscription_path, class: "btn btn-primary" %p = link_to "Cancel my subscription", subscription_path, method: "DELETE", class: "btn btn-danger", data: {confirm: 'Ar sure?'} - else - if @org.trial_ended? %h2 Thank you for trialing our product %h4 To continue using our product please subscribe to the following plan: - else %h2== Thank you for trialing our product (#{distance_of_time_in_words(@org.created_at, Time.zone.now)} left) %h4 To continue using our product after trial please subscribe to the following plan: %p $100/month %p Up to 500 devices and 200 users %p 3 admin users %p CSV/XLS upload = link_to "Subscribe", new_subscription_path, class: 'btn btn-primary'

Slide 33

Slide 33 text

- if current_organisation_subscribed? #... - if @org.last_charge #... - elsif @org.last_charge_status != 'success' #... #... - if @org.expires_at #... - else - if @org.trial_ended? #... - else #... #...

Slide 34

Slide 34 text

if current_user.active && current_user.account && !current_user.account.deleted && current_org.trial_ends_at > Time.current

Slide 35

Slide 35 text

Every flag doubles the number of states .trial_ended? .expired? .cancelled? .subscription_expired? 4 flags = 16 states!

Slide 36

Slide 36 text

Flags can be hidden dates ranges, associations, status fields - if current_organisation_subscribed? #... - if @org.last_charge #... - elsif @org.last_charge_status != 'success' #... #... - if @org.expires_at #... - else - if @org.trial_ended? #... - else #... #...

Slide 37

Slide 37 text

State Machines

Slide 38

Slide 38 text

State Machines

Slide 39

Slide 39 text

State Machines

Slide 40

Slide 40 text

State Machines

Slide 41

Slide 41 text

gem ‘aasm’ gem ‘state-machines’

Slide 42

Slide 42 text

state_machine :state, initial: :trial do state :trial state :trial_ended state :subscribed state :cancelled_subscription state :not_subscribed state :payment_error event :subscribe do transition [:trial, :not_subscribed, :trial_ended] => :subscribed end event :disable_trial do transition :trial => :trial_ended end event :usage_period_ended do transition :cancelled_subscription => :not_subscribed end #... end

Slide 43

Slide 43 text

- if current_org.trial? #... - elsif current_org.trial_ended? #... - elsif current_org.subscribed? #... - elsif current_org.cancelled_subscription? #... - elsif current_org.payment_error? #... - elsif current_org.not_subscribed? #... render partial: current_org.state

Slide 44

Slide 44 text

def something(org) if org.new_record? # ... 16*2 = 32 states!

Slide 45

Slide 45 text

Control the number of degrees of freedom of your app

Slide 46

Slide 46 text

Entry point pressure

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

False assumtions 1 model → 1 controller respond_to

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Small scale respond_to do |format| format.html { render locals: {query: params[:q]} } format.json { @devices = @devices.first(5) @employees = @employees.first(3) } end

Slide 56

Slide 56 text

larger scale (REDMINE) respond_to do |format| format.html { @issue_count = @query.issue_count @issue_pages = Paginator.new @issue_count, per_page_option, params['page'] @issues = @query.issues(:offset => @issue_pages.offset, :limit => @issue_pages.per_page) render :layout => !request.xhr? } format.api { @offset, @limit = api_offset_and_limit @query.column_names = %w(author) @issue_count = @query.issue_count @issues = @query.issues(:offset => @offset, :limit => @limit) Issue.load_visible_relations(@issues) if include_in_api_response?('relations') } format.atom { @issues = @query.issues(:limit => Setting.feeds_limit.to_i) render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.csv { @issues = @query.issues(:limit => Setting.issues_export_limit.to_i) send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') } format.pdf { @issues = @query.issues(:limit => Setting.issues_export_limit.to_i) send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' } end

Slide 57

Slide 57 text

HTML, JSON, PDF, RSS they all used in different ways different subsets of data different ways to generate results So it makes sense to create separate REST representations

Slide 58

Slide 58 text

TOP-LEVEL namespaces class Web::ArticlesController < Web::ApplicationController #... end class Api::ArticlesController < Api::ApplicationController #... end

Slide 59

Slide 59 text

Hierarchy based on “BOUNDED CONTEXTS” scope module: :web do namespace :moderation do resources :articles, only: [:index, :edit, :update, :show] do member do patch :publish end end end resources :articles do member do patch :moderate end scope module: :articles do resources :comments do scope module: :comments do resources :likes, only: [:create] end end end end root 'welcome#index' end

Slide 60

Slide 60 text

Reduce entry point pressure

Slide 61

Slide 61 text

railshurts.com/lifecycle Rails developer Lifecycle

Slide 62

Slide 62 text

Rails developer Lifecycle railshurts.com/lifecycle

Slide 63

Slide 63 text

railshurts.com/lifecycle Rails developer Lifecycle

Slide 64

Slide 64 text

Painless Rails principles 1.Differentiate 'schema' from 'implementation' 2.Reduce entry point pressure 3.Control the number of degrees of freedom of the app 4.Don't mix layers of abstractions 5.Don't fight against the framework railshurts.com/rails-principles

Slide 65

Slide 65 text

Sources SICP (Structure and Interpretation of Computer Programs) TAPL (Types and Programming Languages) by Benjamin C. Pierce DDD (Domain-driven design) by Eric J. Evans CC2e (Code Complete 2nd edition) by Steve McConnell railshurts.com/rails-principles ISO/IEC/ IEEE 42010 Systems and software engineering — Architecture description

Slide 66

Slide 66 text

Email mini-course “Rails Pitfalls”: railshurts.com/rails-pitfalls inem.at, @inem