Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
From The If Jungle to A
Civilised Railway Station
By Botond Orban
About Me
Botond Orban Enthusiast IT Guy, Architect 

Enthusiastic about Ruby

https://github.com/orbanbotond

@orbanbotond
The Origins
The PC made in Ukraine
Code can be written
in a better way!
Railway Oriented Development
With 3 libraries in Parallel!
-classic if jungle
-dry transactions
-monad do notation
-trailb...
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:user]
if package....
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:user]
if package....
True If Jungle
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:us...
Example 1
f(x) = ax+b
No Library
If Jungle
If Jungle Implementation
Specs:
f(a,b) = a+b, a is infinite
context 'add' do
subject { add.call params: params }


context ...
add = ->(params:) do
return { validation: 'must be a real
number' } if params.any?{|x|x.infinite?}
end
Code:
If Jungle Imp...
Specs:
context 'add' do
subject { add.call params: params }
let(:params) { [2,3,4] }
context 'negative cases' do …
context...
Code:
add = ->(params:) do
return { validation: 'must be a real
number' } if params.any?{|x|x.infinite?}
result = params.r...
linear_function = ->(params:) do
result = multiply.call(params: params[-2..-1])
if(result[:operation_result])
return add.c...
Building Blocks
Add Operation

-Guard Condition

-Business Logic: +

Multiply Operation

-Guard Condition

-Business Logic...
1st library
Dry Transactions
Specs:
context 'add' do
subject { DryTransactions::Add.new.call params }


context 'negative cases' do
context 'params inf...
Code:
module DryTransactions
class Add
include Dry::Transaction
step :validate
step :add
private
…
…
Dry Transactions
f(a,...
Railway Oriented Approach
Code:
module DryTransactions
class Add
…
private
def validate(input)
return Failure('must be a real number') unless input....
class LinearOperation
include Dry::Transaction
# a*x
step :multiply
# previous_result + b
step :assembling_partial_results...
class ComplexOperation
include Dry::Transaction
…
def multiply(input)
partialValue = Multiply.new.call(params: [input[:par...
Dry Transaction Implementation
Add Operation

-Guard Step

-Business Logic Step: +

Multiply Operation

-Guard Step

-Busi...
2rd library
Monad Do Notation
Specs:
context 'add' do
subject { MonadDoNotation::Add.new.call params }


context 'negative cases' do
context 'params inf...
Code:
class Add
include Dry::Monads::Result::Mixin
include Dry::Monads::Do::All
def call(arguments)
validation_result = yi...
Code:
class Add
…
def validate(input)
return Failure(validation: 'must be a real
number') unless input.all?{|x|x.finite?}
...
class LinearOperation
include Dry::Monads::Result::Mixin
include Dry::Monads::Do::All
def call(input)
multiplication = yie...
class LinearOperation
include Dry::Monads::Result::Mixin
include Dry::Monads::Do::All
…
private
def multiply(args)
Multipl...
Add Operation

-Validate Method

-Business Logic Method: +

Multiply Operation

-Validate Method

-Business Logic Method: ...
3rd library
Trailblazer Operations
Specs:
context 'add' do
subject { TrailblazerOperations::Add.call params: params }


context 'negative cases' do
context '...
Code:
module TrailblazerOperations
class Add < Trailblazer::Operation
step :validate
step :add
private
…
end
End
Trailblaz...
Railway Oriented Approach
Code:
module TrailblazerOperations
class Add < Trailblazer::Operation
…
private
def validate(options, params:)
unless para...
Code:
class LinearOperation < Trailblazer::Operation
step Nested( Multiply,
input: -> (options, params:, **) do
options.me...
Add Operation

-Validate Step

-Business Logic Step: +

Multiply Operation

-Validate Step

-Business Logic Step: *

Linea...
Dry-Transaction Monad Do Notation Trailblazer
Steps Steps
Ruby Code Wrapped
With Yield
Steps
Code Reuse Ruby Call
Ruby Cod...
True If Jungle
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:us...
Railway Oriented Development
Cyclomatic complexity: 1-2
class Add < Trailblazer::Operation
step :persist_user
step :persis...
Railway Oriented Development
Cyclomatic complexity: 1-2
class Add < Trailblazer::Operation
step :persist_user
failure :log...
Railway Oriented Development
Cyclomatic complexity: 1-2
class Add < Trailblazer::Operation
step :persist_user
failure :log...
Contract for every input (entity & other use case)



Basement:

-Operations for CRUD



Crud (and Other) Reuse (DRY):

-O...
Thank you ;)
Botond Orban Enthusiast IT Guy, Architect 

Enthusiastic about Ruby

https://github.com/orbanbotond

@orbanbo...
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27
Prochain SlideShare
Chargement dans…5
×

The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27

77 vues

Publié le

Speech of Orban Botond, Ruby Developer at Toptal, at Ruby Meditation 27, Dnipro, 19.05.2019
Slideshare -
Next conference - http://www.rubymeditation.com/

Software development is a domain where everybody can make a beautiful sculpture or can quickly build an unsolvable maze. According to my observations the later happens more often unfortunately.
In the title for my presentation both the If Jungle and the Civilised Railway Station are methaphors representing the opposite ends of the scale of quality code.
In my presentation I am going to present my personal experiences on how to get out from the trap of the if jungle by making the code to adhere to the SRP and DRY principles. I am also going to show the advantages of the Railway Oriented Programing using the 3 different libraries.
The examples are going to be stereotypical errors, fun and easy to follow.

Announcements and conference materials https://www.fb.me/RubyMeditation
News https://twitter.com/RubyMeditation
Photos https://www.instagram.com/RubyMeditation
The stream of Ruby conferences (not just ours) https://t.me/RubyMeditation

Publié dans : Technologie
  • Soyez le premier à commenter

The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27

  1. 1. From The If Jungle to A Civilised Railway Station By Botond Orban
  2. 2. About Me Botond Orban Enthusiast IT Guy, Architect Enthusiastic about Ruby
 https://github.com/orbanbotond
 @orbanbotond
  3. 3. The Origins The PC made in Ukraine
  4. 4. Code can be written in a better way!
  5. 5. Railway Oriented Development With 3 libraries in Parallel! -classic if jungle -dry transactions -monad do notation -trailblazer operations
  6. 6. def create user = User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? user.package = package user.save SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end True If Jungle Cyclomatic complexity: 2
  7. 7. def create user = User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? package.user = user package.save if params[:coupon].present? if coupon = Coupon.exists? params[:coupon] discount = Discount.create params[:coupon] package.discount = discount package.save else render … end end SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end True If Jungle Cyclomatic complexity: 4
  8. 8. True If Jungle def create user = User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? package.user = user package.save if params[:coupon].present? if Coupon.exists? params[:coupon] discount = Discount.create params[:coupon] if discount.allowsUser? user package.discount = discount package.save else render ... end else render ... end end SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end Cyclomatic complexity: 5
  9. 9. Example 1 f(x) = ax+b
  10. 10. No Library If Jungle
  11. 11. If Jungle Implementation Specs: f(a,b) = a+b, a is infinite context 'add' do subject { add.call params: params } 
 context 'negative cases' do context 'params infinite' do let(:params) { [(1.0/0.0), 2] } specify 'Be a failure with a proper error message' do expect(subject[:validation]).to eq 'must be a real number' end end end
  12. 12. add = ->(params:) do return { validation: 'must be a real number' } if params.any?{|x|x.infinite?} end Code: If Jungle Implementation f(a,b) = a+b, a is infinite
  13. 13. Specs: context 'add' do subject { add.call params: params } let(:params) { [2,3,4] } context 'negative cases' do … context 'positive cases' do specify 'Be a success with the proper correct output' do expect(subject[:operation_result]).to eq(params.reduce(0) { |acc, x| acc + x }) end end end If Jungle Implementation f(a,b) = a+b,
  14. 14. Code: add = ->(params:) do return { validation: 'must be a real number' } if params.any?{|x|x.infinite?} result = params.reduce(0) { |acc, x| acc + x } return { operation_result: result } end If Jungle Implementation f(a,b) = a+b,
  15. 15. linear_function = ->(params:) do result = multiply.call(params: params[-2..-1]) if(result[:operation_result]) return add.call(params: [result[:operation_result], params[0]]) else return result end end rspec spec/railway_oriented_development/if_jungle_spec.rb ....... 7 examples, 0 failures If Jungle Implementation f(a,b,x) = a.x+b, Linear function
  16. 16. Building Blocks Add Operation -Guard Condition -Business Logic: + Multiply Operation -Guard Condition -Business Logic: * LinearFunction -Delegation -Conditional -Delegation -Return a Result If Jungle Implementation
  17. 17. 1st library Dry Transactions
  18. 18. Specs: context 'add' do subject { DryTransactions::Add.new.call params } 
 context 'negative cases' do context 'params infinite' do let(:params) { {params:[(1.0/0.0), 2]} } specify 'Be a failure with a proper error message' do expect(subject).to be_failure expect(subject.failure).to eq 'must be a real number' end end end Dry Transactions f(a,b) = a+b, a is infinite
  19. 19. Code: module DryTransactions class Add include Dry::Transaction step :validate step :add private … … Dry Transactions f(a,b) = a+b, a is infinite
  20. 20. Railway Oriented Approach
  21. 21. Code: module DryTransactions class Add … private def validate(input) return Failure('must be a real number') unless input.all?{|x| x.finite?} Success(input) end
 def add(input) ret = input.reduce(0) { |acc, x| acc + x } Success(ret) end … Dry Transactions f(a,b) = a+b, a is infinite
  22. 22. class LinearOperation include Dry::Transaction # a*x step :multiply # previous_result + b step :assembling_partial_results private … end Dry Transactions f(a, b, x) = a.x+b, Linear Function
  23. 23. class ComplexOperation include Dry::Transaction … def multiply(input) partialValue = Multiply.new.call(params: [input[:params] [1], input[:params][2]]) partialValue.bind do |value| Success(input.merge(multiplication_result: partialValue.value!)) end end def assembling_partial_results(input) Add.new.call( params: [input[:params][0], input[:multiplication_result]]) end end rspec spec/railway_oriented_development/dry_transactions_spec.rb ....... 7 examples, 0 failures Dry Transactions f(a, b, x) = a.x+b, a is infinite
  24. 24. Dry Transaction Implementation Add Operation -Guard Step -Business Logic Step: + Multiply Operation -Guard Step -Business Step: * LinearFunction -Multiply Step -Assemble Step Note: -no conditional compared to the If Jungle Solution! -linear execution by enlisting the steps!
  25. 25. 2rd library Monad Do Notation
  26. 26. Specs: context 'add' do subject { MonadDoNotation::Add.new.call params } 
 context 'negative cases' do context 'params infinite' do let(:params) { [(1.0/0.0), 2] } specify 'Be a failure with a proper error message' do expect(subject).to be_failure expect(subject.failure[:validation]).to eq 'must be a real number' end end end Monad Do Notation f(a,b) = a+b, a is infinite
  27. 27. Code: class Add include Dry::Monads::Result::Mixin include Dry::Monads::Do::All def call(arguments) validation_result = yield validate(arguments) operation_result = yield add(arguments) Success validation_result.merge( operation_result) end … end Monad Do Notation f(a,b) = a+b, a is infinite
  28. 28. Code: class Add … def validate(input) return Failure(validation: 'must be a real number') unless input.all?{|x|x.finite?} Success(validation: :ok) end def add(input) ret = input.reduce(0) { |acc, x| acc + x } Success(operation_result: ret) end end … Monad Do Notation f(a,b) = a+b, a is infinite
  29. 29. class LinearOperation include Dry::Monads::Result::Mixin include Dry::Monads::Do::All def call(input) multiplication = yield multiply(input[-2..-1]) addition = yield add([input[0], multiplication[:operation_result]]) Success(addition) end private … Monad Do Notation f(a, b, x) = a.x+b,
  30. 30. class LinearOperation include Dry::Monads::Result::Mixin include Dry::Monads::Do::All … private def multiply(args) Multiply.new.call args end def add(args) Add.new.call args end end rspec spec/railway_oriented_development/monad_do_notation_spec.rb ....... 7 examples, 0 failures Monad Do Notation f(a, b, x) = a.x+b,
  31. 31. Add Operation -Validate Method -Business Logic Method: + Multiply Operation -Validate Method -Business Logic Method: * LinearFunction -Multiply Method -Add Method Step Note: -no conditional compared to the If Jungle Solution! -linear execution! -pure Ruby! ***** Monad Do Notation Implementation
  32. 32. 3rd library Trailblazer Operations
  33. 33. Specs: context 'add' do subject { TrailblazerOperations::Add.call params: params } 
 context 'negative cases' do context 'params infinite' do let(:params) { [(1.0/0.0), 2] } specify 'Be a failure with a proper error message' do expect(subject).to be_failure expect(subject[:validation]).to eq 'must be a real number' end end end Trailblazer Operations f(a,b) = a+b, a is infinite
  34. 34. Code: module TrailblazerOperations class Add < Trailblazer::Operation step :validate step :add private … end End Trailblazer Operations f(a,b) = a+b, a is infinite
  35. 35. Railway Oriented Approach
  36. 36. Code: module TrailblazerOperations class Add < Trailblazer::Operation … private def validate(options, params:) unless params.all?{|x|x.finite?} options[:validation] = 'must be a real number' return Railway.fail! end Railway.pass! end def add(options, params:, **rest) ret = params.reduce(0) { |acc, x| acc + x } options[:operation_result] = ret end end end end
  37. 37. Code: class LinearOperation < Trailblazer::Operation step Nested( Multiply, input: -> (options, params:, **) do options.merge params: params[-2..-1] end ) step Nested( Add, input: -> (options, params:, **) do options.to_hash.except(:operation_result).merge params: [params[0], options[:operation_result]] end ) end rspec spec/railway_oriented_development/trailblazer_operations_spec.rb ....... 7 examples, 0 failures Trailblazer Operations f(a, b, x) = a.x+b,
  38. 38. Add Operation -Validate Step -Business Logic Step: + Multiply Operation -Validate Step -Business Logic Step: * LinearFunction -Delegates to the Multiply Operation by Nesting -Delegates to the Add Operation by Nesting Note: -no conditional compared to the If Jungle Solution! -linear execution! -DSL for reuse! Trailblazer Implementation
  39. 39. Dry-Transaction Monad Do Notation Trailblazer Steps Steps Ruby Code Wrapped With Yield Steps Code Reuse Ruby Call Ruby Code Wrapped With Yield DSL for other Operation Reuse!
  40. 40. True If Jungle def create user = User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? package.user = user package.save if params[:coupon].present? if Coupon.exists? params[:coupon] discount = Discount.create params[:coupon] if discount.allowsUser? user package.discount = discount package.save else render ... end else render ... end end SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end Cyclomatic complexity: 5
  41. 41. Railway Oriented Development Cyclomatic complexity: 1-2 class Add < Trailblazer::Operation step :persist_user step :persist_package step :add_coupon_based_discount step :notify_about_registration ... end
  42. 42. Railway Oriented Development Cyclomatic complexity: 1-2 class Add < Trailblazer::Operation step :persist_user failure :log_user_persistance step :persist_package failure :log_package_persistance step :add_coupon_based_discount failure :log_discount_creation step :notify_about_registration ... end
  43. 43. Railway Oriented Development Cyclomatic complexity: 1-2 class Add < Trailblazer::Operation step :persist_user failure :log_user_persistance step :persist_package failure :log_package_persistance step :add_coupon_based_discount failure :log_discount_creation step :notify_about_registration step :notify_facebook_friends … end
  44. 44. Contract for every input (entity & other use case)
 
 Basement:
 -Operations for CRUD 
 Crud (and Other) Reuse (DRY): -Operations for Onboarding -Operations for Admin -Operations for handling Business Use Cases Trailblazer Operations & Contracts (Reform) My Best Practice
  45. 45. Thank you ;) Botond Orban Enthusiast IT Guy, Architect Enthusiastic about Ruby
 https://github.com/orbanbotond
 @orbanbotond

×