Build and maintain large ruby applications - LA Ruby Oct meetup

98
build and maintain large Ruby applications Enrico Teotti - @agenteo - http://teotti.com [email protected] starting shortly

Transcript of Build and maintain large ruby applications - LA Ruby Oct meetup

build and maintain large Ruby applications

Enrico Teotti - @agenteo - http://teotti.com [email protected]

starting shortly

build and maintain large Ruby applications

Enrico Teotti - @agenteo - http://teotti.com

http://www.slideshare.net/agenteo/build-and-maintain-large-ruby-applications-ruby-conf-australia-2016

crowbar.rb spec.description = ”Builds and maintains large Ruby apps”

–Any Java developer always ;-)

“Ruby is a toy language.”

automated testing

team diligence

local Ruby gems

Booking + Driving + Billing

Booking + Driving + Billing

Booking Driving Billing

Ruby files in a project are like ingredients in a recipe

yeasthoney salt

milk flourwaterlard

sugar

arugula

squacqueroneprosciutto

piadinayeasthoney salt

milk flourwaterlard

sugar

arugula

squacqueroneprosciutto

3 months later

piadina

the curse of knowledge

yeasthoney salt

milk flourwaterlard

sugar

arugula

squacqueroneprosciutto

6 months later

– The law of continuing change (1974) Lehman, M

“Any software system used in the real-world must change or become less and less useful in that environment.”

– The law of increasing complexity (1974) Lehman, M

“As a program evolves, it becomes more complex, and extra resources are needed to preserve and simplify its structure.”

biscuits

mozzarella

sunflower oil

carrots

eggs

tomato puree

basil

mascarpone

coffee

cacao

yeasthoney salt

milk flourwaterlard

sugar

arugula

squacqueroneprosciutto

oregano

sunflower oil

carrots

eggs

tomato puree

basil

mascarpone

coffee

cacao

yeasthoney salt

milk flourwaterlard

sugar

arugula

squacqueroneprosciutto

oregano

piadina

pizza margherita

tiramisu

carrot cake

white ingredients

green ingredients

red ingredientsyellowish ingredients

orange ingredients

dark ingredients

white ingredientsgreen ingredients

classes grouped by design pattern

ls -l app/ controllers helpers models presenters services serializers strategies utils views

http://teotti.com/application-directories-named-as-architectural-patterns-antipattern/

piadina

pizza margherita

tiramisu

carrot cake

namespaces

# lib/blog/after_publish.rbmodule Blog class AfterPublish private def subscribe_blogger_to_promotion Promotions::Submission.new end endend

# lib/promotions/new_member.rbmodule Promotions class Submission private def fetch_member(id) # lib/membership/finder.rb Membership::Finder.new(id) end endend

promotionsblog membership

namespacescontext context

# lib/blog/after_publish.rbmodule Blog class AfterPublish private def subscribe_blogger_to_promotion Promotions::Submission.new end endend

# lib/promotions/new_member.rbmodule Promotions class Submission private def fetch_member(id) # lib/membership/finder.rb Membership::Finder.new(id) end endend

promotionsblog membership

namespacescontext context

promotions name finderblog membership

main Ruby application

1 year

lib

promotions name finderblog membership

main Ruby application

1 year

lib

promotions room decorator

name finderblog membershiprecipes

main Ruby application

comments

lib

3 years

promotions room decorator

name finderblog membershiprecipes

main Ruby application

comments

lib

3 years

that’s the Ruby way

is that the Ruby way?

piadina worktop

tiramisu worktop

shared worktop

carrot cake worktop

pizza worktop

local Ruby gems

A

main Ruby application

piadina gem

Apiadina gem

pizza gem

C

shared ingredients gem

B

main Ruby application

Apiadina gem

pizza gem

C

shared ingredients gem

B

main Ruby application

spec.add_dependency "shared_ingredients"

# local_gems/pizza/pizza.gemspec

Apiadina gem

pizza gem

C

shared ingredients gem

B

main Ruby application

spec.add_dependency "shared_ingredients"

# local_gems/piadina/piadina.gemspec

piadina gem

pizza gem

C

shared ingredients gem

B

main Ruby application

desserts gem

DA

piadina gem

pizza gem

C

shared ingredients gem

B

main Ruby application

desserts gem

DA

E

calzone gem

pizza dough gem

F

Conway’s Law“organizations which design systems … are constrained to produce designs which

are copies of the communication structures of these organizations"

piadina gem

pizza gem

shared ingredients gem

main Ruby application

desserts gem

DA

calzone gem

pizza dough gem

F

B

E

C

http://teotti.com/create-dependency-structures-with-local-ruby-gems/

code & examples

A

C

D

B

E

your health plan

drug information

claims platform

product information

membership

gem

gem

gem

gem

gem

dependency

main Ruby application

Sinatra / Rails / Hanami

http://teotti.com/create-dependency-structures-with-local-ruby-gems/

!"" Gemfile!"" Gemfile.lock!"" local_gems!"" run.rb#"" spec

ruby script that triggers entry point

gem’s behaviour

require 'health_plan'

subscriber_id = 'ASE123456789'aggregated_drug_information = HealthPlan::Aggregator.new(subscriber_id)puts aggregated_drug_information.details

main Ruby application

!"" Gemfile!"" Gemfile.lock!"" local_gems!"" run.rb#"" spec

path 'local_gems' do gem 'health_plan'end

source 'https://rubygems.org'

group :test do gem 'rspec'end

bundler’s Gemfile uses a path directive to find

local gems

main Ruby application

!"" Gemfile!"" Gemfile.lock!"" local_gems!"" run.rb#"" spec

bundler’s Gemfile uses a path directive to find

local gems

path 'local_gems' do gem 'health_plan'end

source 'https://rubygems.org'

group :test do gem 'rspec'end

main Ruby application

!"" Gemfile!"" Gemfile.lock!"" local_gems!"" run.rb#"" spec

directory where your local gems are

$ cd local_gems$ bundle gem health_plan create health_plan/Gemfile create health_plan/Rakefile create health_plan/LICENSE.txt create health_plan/README.md create health_plan/.gitignore create health_plan/health_plan.gemspec create health_plan/lib/health_plan.rb create health_plan/lib/health_plan/version.rbInitializing git repo in /Users/me/code/lab/gem-dependency-structure/local_gems/health_plan$ rm -Rf health_plan/.git*

bundle gem can create gems

main Ruby application

# local_gems/health_plan/health_plan.gemspeclib = File.expand_path('../lib', __FILE__)$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)require 'health_plan/version'

Gem::Specification.new do |spec| spec.name = "health_plan" spec.version = HealthPlan::VERSION spec.authors = ["Enrico Teotti"] spec.email = ["[email protected]"] spec.summary = %q{Write a short summary. Required.} spec.description = %q{Write a longer description. Optional.} spec.homepage = "" spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 1.7" spec.add_development_dependency "rake", "~> 10.0"

!"" Gemfile!"" Gemfile.lock!"" local_gems$   #"" health_plan!"" run.rb#"" spec

spec.add_development_dependency "rspec", "3.4.0"end

your health plan

# local_gems/health_plan/health_plan.gemspeclib = File.expand_path('../lib', __FILE__)$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)require 'health_plan/version'

Gem::Specification.new do |spec| spec.name = "health_plan" spec.version = HealthPlan::VERSION spec.authors = ["Enrico Teotti"] spec.email = ["[email protected]"] spec.summary = %q{Write a short summary. Required.} spec.description = %q{Write a longer description. Optional.} spec.homepage = "" spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 1.7" spec.add_development_dependency "rake", "~> 10.0"

!"" Gemfile!"" Gemfile.lock!"" local_gems$   #"" health_plan!"" run.rb#"" spec

spec.add_development_dependency "rspec", "3.4.0"end

your health plan

# local_gems/health_plan/spec/health_plan/aggregator_spec.rbrequire 'spec_helper'

describe HealthPlan::Aggregator do

describe "#details" do it "should not throw exceptions" do aggregator = HealthPlan::Aggregator.new(12345) expect(aggregator.details).to eq({ name: 'The full package plan'}) end end

end

your health plan

!"" Gemfile!"" Gemfile.lock!"" local_gems$   #"" health_plan!"" run.rb#"" spec

# local_gems/health_plan/lib/health_plan/aggregator.rbmodule HealthPlan

class Aggregator def initialize(id) @subscriber_id = id end

def details { name: 'The full package plan'} end endend

# local_gems/health_plan/lib/health_plan.rbrequire "health_plan/version"require "health_plan/aggregator"

module HealthPlanend

gem entry point

your health plan

!"" Gemfile!"" Gemfile.lock!"" local_gems$   #"" health_plan!"" run.rb#"" spec

!"" Gemfile!"" Gemfile.lock!"" local_gems$   !"" drug_information$   #"" health_plan!"" run.rb#"" spec

your health plan

drug information

main Ruby application

$ cd local_gems$ bundle gem drug_information create drug_information/Gemfile create drug_information/Rakefile create drug_information/LICENSE.txt create drug_information/README.md create drug_information/.gitignore create drug_information/drug_information.gemspec create drug_information/lib/drug_information.rb create drug_information/lib/drug_information/version.rb

drug information

# local_gems/health_plan/spec/health_plan/aggregator_spec.rbrequire 'spec_helper'

describe HealthPlan::Aggregator do

describe "#details" do let(:fetched_drugs) { 'something' } before do fetcher_double = double('DrugInformation::Fetcher', details: fetched_drugs) allow(DrugInformation::Fetcher).to receive(:new).and_return(fetcher_double) end

it "should not throw exceptions" do aggregator = HealthPlan::Aggregator.new(12345) expect(aggregator.details).to eq({ name: 'The full package plan’,

drugs: fetched_drugs }) end end

end

your health plan

!"" Gemfile!"" Gemfile.lock!"" local_gems$   !"" drug_information$   #"" health_plan!"" run.rb#"" spec

your health plan

lib = File.expand_path('../lib', __FILE__)$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)require 'health_plan/version'

Gem::Specification.new do |spec| spec.name = "health_plan" spec.version = HealthPlan::VERSION spec.authors = ["Enrico Teotti"] spec.email = ["[email protected]"] spec.summary = %q{Write a short summary. Required.} spec.description = %q{Write a longer description. Optional.} spec.homepage = "" spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 1.7" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "3.4.0"

spec.add_dependency "drug_information"end

# local_gems/health_plan/health_plan.gemspec

!"" Gemfile!"" Gemfile.lock!"" local_gems$   !"" drug_information$   #"" health_plan!"" run.rb#"" spec

your health plan

# local_gems/health_plan/Gemfile!"" Gemfile!"" Gemfile.lock!"" local_gems$   !"" drug_information$   #"" health_plan!"" run.rb#"" spec

path '..'

source 'https://rubygems.org'

‘..’ represents the parent directory

# local_gems/health_plan/lib/health_plan/aggregator.rbmodule HealthPlan

class Aggregator def initialize(id) @subscriber_id = id end

def details fetched_drug_info = DrugInformation::Fetcher.new(@subscriber_id) { name: 'The full package plan', drugs: fetched_drug_info.details } end endend

# local_gems/health_plan/lib/health_plan.rbrequire "health_plan/version"require "health_plan/aggregator"

require "drug_information"

module HealthPlanend

gem entry point

your health plan

!"" Gemfile!"" Gemfile.lock!"" local_gems$   !"" drug_information$   #"" health_plan!"" run.rb#"" spec

require dependent gem

http://teotti.com/create-dependency-structures-with-local-ruby-gems/

automated testing

A

C

D

B

E

main Ruby application

unit tested

unit tested

unit testedunit tested

unit tested

acceptance tests

A

C

main Ruby application

B

loaded in memory, deamon or webserver

unit tested

unit tested

not unit tested

http://teotti.com/create-dependency-structures-with-local-ruby-gems#gotcha-flaky-bugs-caused-by-missing-requirement-statements

A

C

D

B

E

Jurassic Ruby application

developer

CEO

head of product

QA “resource”

QA “resource”

team diligence

A

C

D

B

E

main Ruby application

F

H

I L

membership payment API

payment platform

bank transaction

credit card transaction

your health plan API

drug information

claims platform

product information

membership

A

C

D

B

E

main Ruby application

F

H

I L

membership payment API

payment platform

bank transaction

credit card transaction

your health plan API

drug information

claims platform

product information

membership

A

C

D

B

E

main Ruby application

F

H

I L

membership payment API

payment platform

bank transaction

credit card transaction

your health plan API

drug information

claims platform

product information

membership

A

C

D

B

E

main Ruby application

F

H

I L

membership payment API

payment platform

bank transaction

credit card transaction

your health plan API

drug information

claims platform

product information

membership

A

C

D

B

E

main Ruby application

F

H

I L

membership payment API

payment platform

bank transaction

credit card transaction

your health plan API

drug information

claims platform

product information

membership

A

C

D

B

E

main Ruby application

F

H

I L

I find your use of Gems disturbing

Do I really look like a guy with a plan?

*nods then deletes your

Gem*

http://teotti.com/rails-service-oriented-architecture-alternative-with-components/

to be continued…

premature use of SOA

A

C

D

B

E

main Ruby application

F

H

I L

membership payment API

payment platform

bank transaction

credit card transaction

your health plan API

drug information

claims platform

product information

membership

main Ruby application

your health plan API

drug information

claims platform

product information membership

membership payment

APIpayment platform

bank transaction

credit card transaction

deploy parts of a monolith

http://teotti.com/deploy-parts-of-a-ruby-on-rails-application/

monolithic Ruby application

DB

editorial admin ui

persistence

site search

componentized Ruby application

public content ui

shared ui

DB

deploy@adminServer $ RUNNING_MODE=admin puma

editorial admin ui

persistence

site search

Rails application

public content ui

shared ui

DB

deploy@publicServer $ RUNNING_MODE=public puma

http://teotti.com/deploy-parts-of-a-ruby-on-rails-application/

legacy migration

editorial admin ui

persistence

site search

Rails application

public content ui

shared ui

DB

legacy migration

AWS SQS

massage and transform content

legacy system

pull legacy content

http://cbra.info

formerly lotus.rbhanami.rb

automated testing

team diligence

local Ruby gems

automated testing

team diligence

local Ruby gems

team in a

fixed mindset

run an experiment

automated testing

team diligence

local Ruby gems

automated testing

team diligence

local Ruby gems

automated testing

team diligence

local Ruby gems

Enrico Teotti - @agenteo - http://teotti.com