Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Functional Web with Hanami

Luca Guidi
September 23, 2017

Functional Web with Hanami

Web applications are complex. MVC helps you to structure them, but you're still you're struggling with tangled dependencies, difficult reusability, byzantine patterns, hard to maintain model domains?

What if you can have small, simple, and deterministic components that can be easily used (and reused) in your web apps?

Let's see how functions, FP/OOP blending, data transformations, immutability, Dependency Injection can help to solve these problems.

With this talk you'll learn how Hanami makes these principle easy to use, and what's the future of this uprising Ruby web framework.

Luca Guidi

September 23, 2017
Tweet

More Decks by Luca Guidi

Other Decks in Programming

Transcript

  1. Non-pure (3 of 4) it "returns a different hash for

    another file" do append(testfile, "lorem ipsum..") # ... end
  2. Non-pure (4 of 4) it “raises an error when file

    is missing" do delete(testfile) # ... end
  3. before do touch(testfile) end let(:testfile) { "path/to/testfile" } it "returns

    a hash for a given file" do # ... end it "returns a different hash for another file” do append(testfile, "lorem ipsum..") # ... end it "raises an error when file is missing" do delete(testfile) # ... end Pure vs non-pure it "returns 2 + 2" do expect(sum(2, 2)).to be(4) end
  4. 38

  5. Which kind of functions would you like to see more

    in your apps? Pure or non- pure?
  6. String#capitalize string = "hello world" string.capitalize # => "Hello world"

    string.capitalize # => "Hello world" string.capitalize # => "Hello world"
  7. Deterministic (1 of 2) class Formatter def initialize(string) @string =

    string end def capitalize @string.capitalize end end
  8. Deterministic (2 of 2) formatter = Formatter.new("hello world") formatter.capitalize #

    => "Hello world" formatter.capitalize # => "Hello world" formatter.capitalize # => "Hello world"
  9. Non-deterministic (2 of 2) formatter = Formatter.new("hello world") formatter.capitalize #

    => "Hello world" formatter.replace("foo") formatter.capitalize # => "Foo" ⚠⚠⚠
  10. 47

  11. class User < ORM def initialize(attributes) @attributes = attributes end

    def capitalize @attributes.fetch(:name).capitalize end def update_attributes(other) @attributes.merge!(other) end end
  12. Hanami (1 of 3) class User < Hanami::Entity def capitalize

    name.capitalize end end repository = UserRepository.new
  13. Hanami (2 of 3) # create user = repository.create(params[:user]) user.id

    # => 1 user.capitalize # => "Luca" user.capitalize # => "Luca" user.capitalize # => "Luca"
  14. Hanami (3 of 3) # update user = repository.update( params[:id],

    params[:user]) user.id # => 1 user.capitalize # => “Luca Guidi" user.capitalize # => "Luca Guidi" user.capitalize # => "Luca Guidi”
  15. 53

  16. ->(a, b) { a + b }.call(2, 2) # =>

    4 method(:add).call(2, 2) # => 4 Sum.new.call(2, 2) # => 4 Sum.call(2, 2) # => 4
  17. ->(a, b) { a + b }.call(2, 2) # =>

    4 method(:add) .call(2, 2) # => 4 Sum.new .call(2, 2) # => 4 Sum .call(2, 2) # => 4
  18. sum = ->(a, b) { a + b } sum.call(1,

    2) # => 3 (1..20).inject(&sum) # => 210
  19. multi = ->(a, b) { a * b } multi.(2,

    3) # => 6 pow = ->(a, n) do n.times.inject(1) do |memo| memo = multi.(memo, a) end end pow.(2, 5) # => 32 pow.(2, 0) # => 1 # quadratic equations...
  20. class CompareNumbers def call(a, b) a <=> b end def

    to_proc method(:call).to_proc end end CompareNumbers.new.call(23, 11) # => 1 [23, 11, 99].sort(&CompareNumbers.new) # => [11, 23, 99]
  21. 66

  22. 1. Route the request 2. Verify the request (verb, MIME)

    3. Authentication 4. Coerce & validate input 5. Transform input 6. Verify preconditions (rules) 7. CRUD 8. Trigger postconditions 9. Present the output
  23. 82 Task Component Route the request Hanami::Router HTTP verb/MIME Hanami::Action

    Authentication (custom) Coerce & validate Hanami::Validations Transform (missing) Preconditions (custom) CRUD Hanami::Repository Postconditions (missing) Present Hanami::View
  24. 83 Task Component Func? Route the request Hanami::Router ✅ yes

    HTTP verb/MIME Hanami::Action ❌ no Authentication (custom) ❗not enforced Coerce & validate Hanami::Validations ❌ no Transform Hanami::Transformer in progress Preconditions (custom) ❗not enforced CRUD Hanami::Repository ✅ yes Postconditions Hanami::Events in progress Present Hanami::View ❌ no
  25. 84

  26. Current design class MyValidator include Hanami::Validations validations do required(:name).filled(:str?) end

    end result = MyValidator.new(name: "Luca").validate result.successful? # => true
  27. Future design class MyValidator < Hanami::Validator validations do required(:name).filled(:str?) end

    end result = MyValidator.new.call(name: "Luca") result.successful? # => true
  28. Self-registering (2 of 3) module Web::Controllers::Users class Create include Web::Action

    def call(params) UserRepository.new.create(params[:user]) end end end
  29. Self-registering (3 of 3) module Web::Controllers::Users class Create < Hanami::Action

    import :users def call(params) users.create(params[:user]) end end end
  30. 90 Self-registration is possible via a fine tuned mechanism, which

    is able to resolve a tree of dependencies and to inject them.
  31. 92 Task Pure? Route the request ✅ yes HTTP verb/MIME

    ✅ yes Authentication ✅ yes Coerce & validate ✅ yes Transform ✅ yes Preconditions ✅ yes CRUD ❌ no* Postconditions ❌ no* Present ✅ yes
  32. 93 Task Pure? MVC Route the request ✅ yes Controller

    HTTP verb/MIME ✅ yes Controller Authentication ✅ yes Controller Coerce & validate ✅ yes Model Transform ✅ yes Model Preconditions ✅ yes Model CRUD ❌ no* Model Postconditions ❌ no* Model Present ✅ yes View
  33. 97 A pipeline is a set of compatible components that

    are executed in a specific order. If one of those steps returns an error, the execution is stopped.
  34. 1. Route the request 2. Verify the request (verb, MIME)

    3. Authentication 4. Coerce & validate input 5. Transform input 6. Verify preconditions (rules) 7. CRUD 8. Trigger postconditions 9. Present the output
  35. Pipeline (1 of 2) class Signup < Hanami::Interactor import :validator,

    :transformer, :users pipeline :validator, :transformer, :persist, :notify private def persist(input:, output:) output[:user] = users.create(input) end def notify(output:, **) events.broadcast(“signup", user_id: output[:user].id) end end
  36. Pipeline (2 of 2) module Web::Controllers::Users class Create < Hanami::Action

    import :signup def call(params) result = signup.call(params[:signup]) if result.successful? # ... else # ... end end end end
  37. 101