Date post: | 15-Apr-2017 |
Category: |
Technology |
Upload: | enrico-teotti |
View: | 866 times |
Download: | 0 times |
build and maintain large Ruby applications
Enrico Teotti - @agenteo - http://teotti.com
starting shortly
build and maintain large Ruby applications
Enrico Teotti - @agenteo - http://teotti.comhttp://www.slideshare.net/agenteo
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
http://teotti.com/cognitive-overload-in-software-development/
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
promotions name finderblog membership
main Ruby application
1 year
lib
http://teotti.com/building-and-maintaing-large-ruby-on-rails-applications-for-3-years/
DB
team of 5
promotions room decorator
name finderblog membershiprecipes
main Ruby application
comments
3 years
lib
DB
team of 5
that’s the Ruby wayma noooooooooo
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
main Ruby application
spec.add_dependency "shared_ingredients"
# local_gems/piadina/piadina.gemspec
B
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
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
!"" 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
http://teotti.com/gemfiles-hierarchy-in-ruby-on-rails-component-based-architecture/
!"" 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
gemspec
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'
look for dependencies within the gem specification file
gemspec
# 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/
$ bundle viz
!"" Gemfile!"" Gemfile.lock!"" local_gems$ !"" drug_information$ #"" health_plan!"" run.rb#"" spec
main Ruby application
https://github.com/shageman/cobradeps
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
team diligence
A
C
D
B
E
main Ruby application
your health plan API
drug information
claims platform
product information
persistence DB
A
C
D
B
E
main Ruby application
F
H
I L
modular monolith!
membership payment API
payment platform
bank transaction
credit card transaction
your health plan API
drug information
claims platform
product information
persistence DDB
A
C
D
B
E
main Ruby application
F
H
I L
payment platform
A
C
D
B
E
main Ruby application
F
H
I L
your health plan API
DDB
A
C
D
B
E
main Ruby application
F
H
I L
membership payment API
DDB
A
C
D
B
E
main Ruby application
F
H
I LDDB
A
C
D
B
E
main Ruby application
F
H
I LDDB
A
C
D
B
E
main Ruby application
F
H
I LDDB
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*
DDB
Director of security
Director of happiness
Director of project Death Star
team diligence
http://blog.codinghorror.com/the-last-responsible-moment/
team diligence
http://teotti.com/rails-service-oriented-architecture-alternative-with-components/
to be continued…
premature use of SOA in a small team
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
persistence DDB
main Ruby application
your health plan API
drug information
claims platform
product information persistence
membership payment
APIpayment platform
bank transaction
credit card transaction
DB
deploy parts of a monolith
http://teotti.com/deploy-parts-of-a-ruby-on-rails-application/
monolithic Ruby application
DB
editorial admin
persistence
site search
componentized Ruby application
public content
shared ui
DB
editorial admin
persistence
site search
componentized Ruby application
public content ui
shared ui
DB
Persistence::ContentPieceRepository.create(params)
editorial admin
persistence
site search
componentized Ruby application
public content
shared ui
DB
editorial admin
persistence
site search
componentized Ruby application
public content
shared ui
DB
editorial admin ui
persistence
site search
componentized Ruby application
public content
shared ui
DB
Persistence::ContentPieceRepository.find_by_slug('/article-slug')
deploy@adminServer $ RUNNING_MODE=admin puma
editorial admin
persistence
site search
Rails application
public content
shared ui
DB
deploy@publicServer $ RUNNING_MODE=public puma
editorial admin
persistence
site search
componentized Ruby application
public content
shared ui
DB
editorial admin
persistence
site search
componentized Ruby application
public content
shared ui
DB
legacy migration
editorial admin ui
persistence
site search
componentized Ruby application
public content ui
shared ui
DB
legacy migration
AWS SQS
massage and transform content
legacy system
pull legacy content
automated testing
team diligence
local Ruby gems
automated testing
team diligence
local Ruby gems
team in a
fixed mindset
gems require a bit of experience,gems require you to work a bit hard,
this person doesn’t write maintainable code yet,that person doesn’t understand ruby gems yet
gems require talented developersgems are only for smart developers,
this person always write unmaintainable code,that person will never understand ruby gems
https://www.youtube.com/watch?v=W47rcJowx7k
http://www.amazon.com/Mindset-The-New-Psychology-Success/dp/0345472322
automated testing
team diligence
local Ruby gems
automated testing
team diligence
local Ruby gems
automated testing
team diligence
local Ruby gems
More Links
https://leanpub.com/cbra http://teotti.com/component-based-rails-architecture-primer/ http://teotti.com/reengineer-legacy-rails-applications/