Slide 1

Slide 1 text

Middleware A general abstraction.

Slide 2

Slide 2 text

Mitchell Hashimoto @mitchellh

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Large Classes

Slide 7

Slide 7 text

Large Classes

Slide 8

Slide 8 text

Large Classes

Slide 9

Slide 9 text

Large Classes

Slide 10

Slide 10 text

Large Classes

Slide 11

Slide 11 text

Large Classes

Slide 12

Slide 12 text

Problems:

Slide 13

Slide 13 text

1. Unapproachable

Slide 14

Slide 14 text

2. Unclear dependencies

Slide 15

Slide 15 text

3. Difficult to Test

Slide 16

Slide 16 text

Mixin all the things!

Slide 17

Slide 17 text

1 module Authlogic 2 module Session # :nodoc: 3 # This is the base class Authlogic, where all modules are included. 4 # 5 class Base 6 include Foundation 7 include Callbacks 8 9 # Included first so that the session resets itself to nil 10 include Timeout 11 12 # Included in a specific order so they are tried in this order when persisting 13 include Params 14 include Cookies 15 include Session 16 include HttpAuth 17 18 # Included in a specific order so magic states gets ran after a record is found 19 include Password 20 include UnauthorizedRecord 21 include MagicStates 22 23 include Activation 24 include ActiveRecordTrickery 25 include BruteForceProtection 26 include Existence 27 include Klass 28 include MagicColumns 29 include PerishableToken 30 include Persistence 31 include Scopes 32 include Id 33 include Validation 34 include PriorityRecord 35 end 36 end 37 end

Slide 18

Slide 18 text

Function Composition F*** Yeah.

Slide 19

Slide 19 text

1 def f(value) 2 value + 1 3 end 4 5 def g(value) 6 value * 2 7 end

Slide 20

Slide 20 text

1 f(g(1)) # => 3 2 3 g(f(1)) # => 4

Slide 21

Slide 21 text

1 f(g(1)) # => 3 2 3 g(f(1)) # => 4 Clear Ordering

Slide 22

Slide 22 text

1 f(g(1)) # => 3 2 3 g(f(1)) # => 4 Clear Dependencies

Slide 23

Slide 23 text

1 f(g(1)) # => 3 2 3 g(f(1)) # => 4 Easy to Test

Slide 24

Slide 24 text

1 f(g(1)) # => 3 2 3 g(f(1)) # => 4 True Separation of Logic

Slide 25

Slide 25 text

Function Composition Applied to Large Classes?

Slide 26

Slide 26 text

Middleware

Slide 27

Slide 27 text

Rack Middleware

Slide 28

Slide 28 text

Python Web Server Gateway Interface v1.0 python.org/dev/peps/pep-0333/

Slide 29

Slide 29 text

Request Processing

Slide 30

Slide 30 text

1 app = Rack::Builder.new do 2 use Middleware::Example 3 use Rack::CommonLogger 4 use Rack::ShowExceptions 5 end

Slide 31

Slide 31 text

1 app = Rack::Builder.new do 2 use Middleware::Example 3 use Rack::CommonLogger 4 use Rack::ShowExceptions 5 end

Slide 32

Slide 32 text

1 app = Rack::Builder.new do 2 use Middleware::Example 3 use Rack::CommonLogger 4 use Rack::ShowExceptions 5 end

Slide 33

Slide 33 text

1 module Middleware 2 class Example 3 def initialize(app) 4 @app = app 5 end 6 7 def call(env) 8 # do something before the next middleware 9 # possibly modify the environment 10 11 # run the next middleware in the stack 12 @app.call(env) 13 14 # do something after the next middleware 15 end 16 end 17 end

Slide 34

Slide 34 text

1 module Middleware 2 class Example 3 def initialize(app) 4 @app = app 5 end 6 7 def call(env) 8 # do something before the next middleware 9 # possibly modify the environment 10 11 # run the next middleware in the stack 12 @app.call(env) 13 14 # do something after the next middleware 15 end 16 end 17 end

Slide 35

Slide 35 text

1 module Middleware 2 class Example 3 def initialize(app) 4 @app = app 5 end 6 7 def call(env) 8 # do something before the next middleware 9 # possibly modify the environment 10 11 # run the next middleware in the stack 12 @app.call(env) 13 14 # do something after the next middleware 15 end 16 end 17 end

Slide 36

Slide 36 text

1 module Middleware 2 class Example 3 def initialize(app) 4 @app = app 5 end 6 7 def call(env) 8 # do something before the next middleware 9 # possibly modify the environment 10 11 # run the next middleware in the stack 12 @app.call(env) 13 14 # do something after the next middleware 15 end 16 end 17 end

Slide 37

Slide 37 text

1 app = Rack::Builder.new do 2 use Middleware::Example 3 use Rack::CommonLogger 4 use Rack::ShowExceptions 5 end

Slide 38

Slide 38 text

Why is middleware so amazing?

Slide 39

Slide 39 text

Explicit Ordering

Slide 40

Slide 40 text

1 app = Rack::Builder.new do 2 use Middleware::Example 3 use Rack::CommonLogger 4 use Rack::ShowExceptions 5 end

Slide 41

Slide 41 text

Visible Dependencies

Slide 42

Slide 42 text

1 module Middleware 2 class Example 3 # ... 4 5 def call(env) 6 if env["path"] == "/foo" 7 env["path"] = "/bar" 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 43

Slide 43 text

1 module Middleware 2 class Example 3 # ... 4 5 def call(env) 6 if env["path"] == "/foo" 7 env["path"] = "/bar" 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 44

Slide 44 text

Easily Tested

Slide 45

Slide 45 text

1 module Middleware 2 class Example 3 # ... 4 5 def call(env) 6 if env["path"] == "/foo" 7 env["path"] = "/bar" 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 46

Slide 46 text

1 state = { "path" => "/foo" } 2 m = Middleware::Example.new 3 m.call(state) 4 assert state["path"] == "/bar"

Slide 47

Slide 47 text

Extensible: Subclass!

Slide 48

Slide 48 text

Vagrant vagrantup.com

Slide 49

Slide 49 text

Virtual Machine Lifecycle

Slide 50

Slide 50 text

$ vagrant suspend

Slide 51

Slide 51 text

1 Builder.new do 2 use General::CheckVirtualbox 3 use General::Validate 4 use VM::CheckAccessible 5 use VM::Suspend 6 end

Slide 52

Slide 52 text

1 Builder.new do 2 use General::CheckVirtualbox 3 use General::Validate 4 use VM::CheckAccessible 5 use VM::Suspend 6 end

Slide 53

Slide 53 text

1 Builder.new do 2 use General::CheckVirtualbox 3 use General::Validate 4 use VM::CheckAccessible 5 use VM::Suspend 6 end

Slide 54

Slide 54 text

1 module Vagrant::Action::General 2 class Validate 3 # ... 4 5 def call(env) 6 if env[:validate] 7 env[:vm].config.validate!(env[:vm].env) 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 55

Slide 55 text

1 module Vagrant::Action::General 2 class Validate 3 # ... 4 5 def call(env) 6 if env[:validate] 7 env[:vm].config.validate!(env[:vm].env) 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 56

Slide 56 text

1 module Vagrant::Action::General 2 class Validate 3 # ... 4 5 def call(env) 6 if env[:validate] 7 env[:vm].config.validate!(env[:vm].env) 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 57

Slide 57 text

1 module Vagrant::Action::General 2 class Validate 3 # ... 4 5 def call(env) 6 if env[:validate] 7 env[:vm].config.validate!(env[:vm].env) 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 58

Slide 58 text

1 Builder.new do 2 use General::CheckVirtualbox 3 use General::Validate 4 use VM::CheckAccessible 5 use VM::Suspend 6 end

Slide 59

Slide 59 text

1 module Vagrant::Action::VM 2 class Suspend 3 # ... 4 5 def call(env) 6 if env[:vm].state == :running 7 env[:vm].driver.suspend 8 end 9 10 @app.call(env) 11 end 12 end 13 end

Slide 60

Slide 60 text

$ vagrant up

Slide 61

Slide 61 text

1 Builder.new do 2 use General::CheckVirtualbox 3 use General::Validate 4 use VM::CheckAccessible 5 use VM::CheckBox 6 use VM::Import 7 use VM::CheckGuestAdditions 8 use VM::DefaultName 9 use VM::MatchMACAddress 10 use VM::CleanMachineFolder 11 use VM::ClearForwardedPorts 12 use VM::CheckPortCollisions 13 use VM::ForwardPorts 14 use VM::Provision 15 use VM::PruneNFSExports 16 use VM::NFS 17 use VM::ClearSharedFolders 18 use VM::ShareFolders 19 use VM::HostName 20 use VM::ClearNetworkInterfaces 21 use VM::Network 22 use VM::Customize 23 use VM::Boot 24 end

Slide 62

Slide 62 text

1 Builder.new do 2 use General::CheckVirtualbox 3 use General::Validate 4 use VM::CheckAccessible 5 use VM::CheckBox 6 use VM::Import 7 use VM::CheckGuestAdditions 8 use VM::DefaultName 9 use VM::MatchMACAddress 10 use VM::CleanMachineFolder 11 use VM::ClearForwardedPorts 12 use VM::CheckPortCollisions 13 use VM::ForwardPorts 14 use VM::Provision 15 use VM::PruneNFSExports 16 use VM::NFS 17 use VM::ClearSharedFolders 18 use VM::ShareFolders 19 use VM::HostName 20 use VM::ClearNetworkInterfaces 21 use VM::Network 22 use VM::Customize 23 use VM::Boot 24 end

Slide 63

Slide 63 text

Scared?

Slide 64

Slide 64 text

Real Example

Slide 65

Slide 65 text

Diaspora user.rb

Slide 66

Slide 66 text

1 def accept_invitation!(opts = {}) 2 if self.invited? 3 self.setup(opts) 4 self.invitation_token = nil 5 self.password = opts[:password] 6 self.password_confirmation = opts[:password_confirmation] 7 8 self.save 9 return unless self.errors.empty? 10 11 # moved old Invitation#share_with! logic into here, 12 # but i don't think we want to destroy the invitation 13 # anymore. we may want to just call self.share_with 14 invitations_to_me.each do |invitation| 15 if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) 16 invitation.destroy 17 end 18 end 19 20 self 21 end 22 end

Slide 67

Slide 67 text

1 def accept_invitation!(opts = {}) 2 if self.invited? 3 self.setup(opts) 4 self.invitation_token = nil 5 self.password = opts[:password] 6 self.password_confirmation = opts[:password_confirmation] 7 8 self.save 9 return unless self.errors.empty? 10 11 # moved old Invitation#share_with! logic into here, 12 # but i don't think we want to destroy the invitation 13 # anymore. we may want to just call self.share_with 14 invitations_to_me.each do |invitation| 15 if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) 16 invitation.destroy 17 end 18 end 19 20 self 21 end 22 end

Slide 68

Slide 68 text

1 def accept_invitation!(opts = {}) 2 if self.invited? 3 self.setup(opts) 4 self.invitation_token = nil 5 self.password = opts[:password] 6 self.password_confirmation = opts[:password_confirmation] 7 8 self.save 9 return unless self.errors.empty? 10 11 # moved old Invitation#share_with! logic into here, 12 # but i don't think we want to destroy the invitation 13 # anymore. we may want to just call self.share_with 14 invitations_to_me.each do |invitation| 15 if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) 16 invitation.destroy 17 end 18 end 19 20 self 21 end 22 end

Slide 69

Slide 69 text

1 def accept_invitation!(opts = {}) 2 if self.invited? 3 self.setup(opts) 4 self.invitation_token = nil 5 self.password = opts[:password] 6 self.password_confirmation = opts[:password_confirmation] 7 8 self.save 9 return unless self.errors.empty? 10 11 # moved old Invitation#share_with! logic into here, 12 # but i don't think we want to destroy the invitation 13 # anymore. we may want to just call self.share_with 14 invitations_to_me.each do |invitation| 15 if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) 16 invitation.destroy 17 end 18 end 19 20 self 21 end 22 end

Slide 70

Slide 70 text

1 def accept_invitation!(opts = {}) 2 if self.invited? 3 self.setup(opts) 4 self.invitation_token = nil 5 self.password = opts[:password] 6 self.password_confirmation = opts[:password_confirmation] 7 8 self.save 9 return unless self.errors.empty? 10 11 # moved old Invitation#share_with! logic into here, 12 # but i don't think we want to destroy the invitation 13 # anymore. we may want to just call self.share_with 14 invitations_to_me.each do |invitation| 15 if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) 16 invitation.destroy 17 end 18 end 19 20 self 21 end 22 end

Slide 71

Slide 71 text

1 def accept_invitation!(opts = {}) 2 if self.invited? 3 self.setup(opts) 4 self.invitation_token = nil 5 self.password = opts[:password] 6 self.password_confirmation = opts[:password_confirmation] 7 8 self.save 9 return unless self.errors.empty? 10 11 # moved old Invitation#share_with! logic into here, 12 # but i don't think we want to destroy the invitation 13 # anymore. we may want to just call self.share_with 14 invitations_to_me.each do |invitation| 15 if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) 16 invitation.destroy 17 end 18 end 19 20 self 21 end 22 end

Slide 72

Slide 72 text

1 def setup(opts) 2 self.username = opts[:username] 3 self.email = opts[:email] 4 self.language = opts[:language] 5 self.language ||= I18n.locale.to_s 6 self.valid? 7 errors = self.errors 8 errors.delete :person 9 return if errors.size > 0 10 self.set_person(Person.new(opts[:person] || {} )) 11 self.generate_keys 12 self 13 end

Slide 73

Slide 73 text

1 class SetupUser 2 def call(env) 3 user = env[:user] 4 5 user.username = env[:username] 6 user.email = env[:email] 7 user.language = env[:language] 8 user.language ||= I18n.locale.to_s 9 user.valid? 10 errors = user.errors 11 errors.delete :person 12 raise ErrorsHappened if errors.size > 0 13 user.set_person(Person.new(env[:person] || {} )) 14 user.generate_keys 15 end 16 end

Slide 74

Slide 74 text

1 class SetupUser 2 def call(env) 3 user = env[:user] 4 5 user.username = env[:username] 6 user.email = env[:email] 7 user.language = env[:language] 8 user.language ||= I18n.locale.to_s 9 user.valid? 10 errors = user.errors 11 errors.delete :person 12 raise ErrorsHappened if errors.size > 0 13 user.set_person(Person.new(env[:person] || {} )) 14 user.generate_keys 15 @app.call(env) 16 end 17 end

Slide 75

Slide 75 text

1 class SetupUser 2 def call(env) 3 user = env[:user] 4 5 user.username = env[:username] 6 user.email = env[:email] 7 user.language = env[:language] 8 user.language ||= I18n.locale.to_s 9 user.valid? 10 errors = user.errors 11 errors.delete :person 12 raise ErrorsHappened if errors.size > 0 13 user.set_person(Person.new(env[:person] || {} )) 14 user.generate_keys 15 @app.call(env) 16 end 17 end

Slide 76

Slide 76 text

1 Builder.new do 2 use HaltIfInvited 3 use SetupUser 4 use SetPassword 5 use ClearInvitations 6 end

Slide 77

Slide 77 text

1 def accept_invitation!(opts={}) 2 run(:accept_invitation, opts.merge(:user => self)) 3 end

Slide 78

Slide 78 text

When and Where

Slide 79

Slide 79 text

Reuse

Slide 80

Slide 80 text

1 Builder.new do 2 use SetupUser 3 use AcceptInvite 4 use ConfirmationEmail 5 end 6 7 Builder.new do 8 use SetupUser 9 use Signup 10 end

Slide 81

Slide 81 text

1 Builder.new do 2 use SetupUser 3 use AcceptInvite 4 use ConfirmationEmail 5 end 6 7 Builder.new do 8 use SetupUser 9 use Signup 10 end

Slide 82

Slide 82 text

Serial Invocations

Slide 83

Slide 83 text

1 def do_something! 2 foo(1) 3 bar(2) 4 baz(3) 5 end 6 7 Builder.new do 8 use Foo, 1 9 use Bar, 2 10 use Baz, 3 11 end

Slide 84

Slide 84 text

Composition

Slide 85

Slide 85 text

1 def do_something! 2 foo(bar(baz(1))) 3 end 4 5 Builder.new { 6 use Baz 7 use Bar 8 use Foo 9 }.call(1)

Slide 86

Slide 86 text

Mitchell Hashimoto @mitchellh THANKS!