$30 off During Our Annual Pro Sale. View Details »

Adventures In The Hexagonal (ShRUG November 12)

Jon Rowe
November 12, 2012

Adventures In The Hexagonal (ShRUG November 12)

I’m passionate about TDD. I hate slow tests. I write app’s that work with Rails. I used to think these three things, (tests,speed,rails) was a classic ‘pick any two problem’, now I don’t.

Let’s talk about building Rails apps; how we can use hexagonal techniques to create code that is loosley coupled, isolated, and hopefully better designed; how we can use the existing Rails ecosystem to achieve this, and hopefully present a few ideas on where we can take this.

Given at the november 2012 meetup of the Sheffield Ruby User Group

Jon Rowe

November 12, 2012
Tweet

More Decks by Jon Rowe

Other Decks in Programming

Transcript

  1. ADVENTURES
    IN THE
    HEXAGONAL
    JON ROWE
    Tuesday, 13 November 12

    View Slide

  2. HEXAWAT?
    Tuesday, 13 November 12

    View Slide

  3. HEXAWAT?
    Who is this crazy person?
    Tuesday, 13 November 12

    View Slide

  4. HEXAWAT?
    Who is this crazy person?
    What is he talking about?
    Tuesday, 13 November 12

    View Slide

  5. HEXAWAT?
    Who is this crazy person?
    What is he talking about?
    Why is the rum gone?
    Tuesday, 13 November 12

    View Slide

  6. HEXAGONAL
    Tuesday, 13 November 12

    View Slide

  7. HEXAGONAL
    Hexagonal Architecture
    Tuesday, 13 November 12

    View Slide

  8. HEXAGONAL
    Hexagonal Architecture
    Describes the structure of an app
    Tuesday, 13 November 12

    View Slide

  9. HISTORY
    Originally by Alistair Cockburn
    Tuesday, 13 November 12

    View Slide

  10. HISTORY
    Originally by Alistair Cockburn
    Also called “Ports and Adaptors”
    Tuesday, 13 November 12

    View Slide

  11. HISTORY
    Back in t’day
    Tuesday, 13 November 12

    View Slide

  12. HISTORY
    Back in t’day
    3 Layer Architecture
    Tuesday, 13 November 12

    View Slide

  13. HISTORY
    Back in t’day
    3 Layer Architecture
    GUI => Logic => DB
    Tuesday, 13 November 12

    View Slide

  14. Application
    Tuesday, 13 November 12

    View Slide

  15. HEXAGONAL
    Tuesday, 13 November 12

    View Slide

  16. HEXAGONAL
    Decouple the application
    Tuesday, 13 November 12

    View Slide

  17. HEXAGONAL
    Decouple the application
    Make the business logic the focus
    Tuesday, 13 November 12

    View Slide

  18. HEXAGONAL
    Decouple the application
    Make the business logic the focus
    Provide adaptors for technology
    Tuesday, 13 November 12

    View Slide

  19. HEXAGONAL
    Decouple the application
    Make the business logic the focus
    Provide adaptors for technology
    Provide adaptors for I/O
    Tuesday, 13 November 12

    View Slide

  20. HEXAGONAL RAILS
    Tuesday, 13 November 12

    View Slide

  21. HEXAGONAL RAILS
    Describes structure of application
    Tuesday, 13 November 12

    View Slide

  22. HEXAGONAL RAILS
    Describes structure of application
    but... Rails is not the application
    Tuesday, 13 November 12

    View Slide

  23. HEXAGONAL RAILS
    Describes structure of application
    but... Rails is not the application
    ... Rails is the HTTP Adaptor
    Tuesday, 13 November 12

    View Slide

  24. HEXAGONAL RAILS
    Describes building apps *with* Rails
    Tuesday, 13 November 12

    View Slide

  25. Application
    Tuesday, 13 November 12

    View Slide

  26. RAILS
    CMD
    LINE
    LOGGING
    DB
    Application
    Tuesday, 13 November 12

    View Slide

  27. WHY?
    Tuesday, 13 November 12

    View Slide

  28. WHY?
    Separate responsibilities
    Tuesday, 13 November 12

    View Slide

  29. WHY?
    Separate responsibilities
    Not just SOC
    Tuesday, 13 November 12

    View Slide

  30. WHY?
    Separate responsibilities
    Not just SOC
    Separate business logic from web logic
    Tuesday, 13 November 12

    View Slide

  31. WHY?
    Tuesday, 13 November 12

    View Slide

  32. WHY?
    Push dependencies inwards
    Tuesday, 13 November 12

    View Slide

  33. WHY?
    Push dependencies inwards
    Make things more explicit
    Tuesday, 13 November 12

    View Slide

  34. WHY?
    Push dependencies inwards
    Make things more explicit
    Easier to see coupling / dependancies
    Tuesday, 13 November 12

    View Slide

  35. WHY?
    Tuesday, 13 November 12

    View Slide

  36. WHY?
    Modular code
    Tuesday, 13 November 12

    View Slide

  37. WHY?
    Modular code
    Isolated / Testable code
    Tuesday, 13 November 12

    View Slide

  38. WHY?
    Modular code
    Isolated / Testable code
    Cleaner code
    Tuesday, 13 November 12

    View Slide

  39. INTERLUDE
    Tuesday, 13 November 12

    View Slide

  40. HOW?
    Tuesday, 13 November 12

    View Slide

  41. HOW?
    Dependency Injection / Inversion
    Tuesday, 13 November 12

    View Slide

  42. HOW?
    Dependency Injection / Inversion
    Pure Ruby service classes
    Tuesday, 13 November 12

    View Slide

  43. HOW?
    Dependency Injection / Inversion
    Pure Ruby service classes
    Define an API / Duck Typing
    Tuesday, 13 November 12

    View Slide

  44. CONTROLLERS
    Tuesday, 13 November 12

    View Slide

  45. CONTROLLERS
    Controller provides what is needed
    Tuesday, 13 November 12

    View Slide

  46. CONTROLLERS
    Controller provides what is needed
    Controller provides how to respond
    Tuesday, 13 November 12

    View Slide

  47. class AccountController < ApplicationController
    def update
    ServiceClass.perform(self,params[:things])
    end
    def success(model)
    redirect_to model
    end
    def failure(model)
    render "edit", locals: { form: model }
    end
    end
    Tuesday, 13 November 12

    View Slide

  48. class HexagonalController < ApplicationController
    def update
    ServiceClass.perform(
    FormResponse.new(self), params[:things]
    )
    end
    end
    class FormResponse < SimpleDelegator
    def success(model)
    redirect_to model
    end
    def failure(model)
    render "edit", locals: { form: model }
    end
    end
    Tuesday, 13 November 12

    View Slide

  49. RESPONDERS
    Tuesday, 13 November 12

    View Slide

  50. RESPONDERS
    Wrap response logic
    Tuesday, 13 November 12

    View Slide

  51. RESPONDERS
    Wrap response logic
    Can be reused
    Tuesday, 13 November 12

    View Slide

  52. RESPONDERS
    Wrap response logic
    Can be reused
    Can be stacked
    Tuesday, 13 November 12

    View Slide

  53. ServiceClass.perform(
    AuthorizationResponse.new(FormResponse.new(self)),
    params[:things]
    )
    class FormResponse < SimpleDelegator
    def success(model)
    redirect_to model
    end
    def failure(model)
    render "edit", locals: { form: model }
    end
    end
    class AuthorizationResponse < SimpleDelegator
    def access_denied
    redirect_to "login"
    end
    end
    Tuesday, 13 November 12

    View Slide

  54. CORE
    Tuesday, 13 November 12

    View Slide

  55. CORE
    Do things without knowledge of Rails
    Tuesday, 13 November 12

    View Slide

  56. CORE
    Do things without knowledge of Rails
    Respond back with results
    Tuesday, 13 November 12

    View Slide

  57. class AnotherServiceClass
    def initialize(responder, model, store)
    @responder, @model, @store = responder, model, store
    end
    def perform(attrs)
    model.new_thing = attrs.fetch :new_thing, 'Default'
    if store.save model
    @responder.success model
    else
    @responder.failure model
    end
    end
    end
    Tuesday, 13 November 12

    View Slide

  58. DEPENDENCY INJECTION
    Tuesday, 13 November 12

    View Slide

  59. DEPENDENCY INJECTION
    Not scary
    Tuesday, 13 November 12

    View Slide

  60. DEPENDENCY INJECTION
    Not scary
    Can be simple plain Ruby
    Tuesday, 13 November 12

    View Slide

  61. DEPENDENCY INJECTION
    Not scary
    Can be simple plain Ruby
    Pass in what you need
    Tuesday, 13 November 12

    View Slide

  62. SIDE NOTE
    Tuesday, 13 November 12

    View Slide

  63. SIDE NOTE
    Use Rack, Use Routes
    Tuesday, 13 November 12

    View Slide

  64. SIDE NOTE
    Use Rack, Use Routes
    Provide info before the controller
    Tuesday, 13 November 12

    View Slide

  65. SIDE NOTE
    Use Rack, Use Routes
    Provide info before the controller
    E.g. Warden / Rack env for users.
    Tuesday, 13 November 12

    View Slide

  66. PERSISTENCE
    Tuesday, 13 November 12

    View Slide

  67. PERSISTENCE
    Not very talked about
    Tuesday, 13 November 12

    View Slide

  68. PERSISTENCE
    Not very talked about
    No consensus
    Tuesday, 13 November 12

    View Slide

  69. PERSISTENCE
    Not very talked about
    No consensus
    Still hard, how do we deal with AR?
    Tuesday, 13 November 12

    View Slide

  70. PERSISTENCE
    Tuesday, 13 November 12

    View Slide

  71. PERSISTENCE
    Wrap ORM API
    Tuesday, 13 November 12

    View Slide

  72. PERSISTENCE
    Wrap ORM API
    Create repositories that pass back
    objects
    Tuesday, 13 November 12

    View Slide

  73. PERSISTENCE
    Wrap ORM API
    Create repositories that pass back
    objects
    Use DataMapper pattern?
    Tuesday, 13 November 12

    View Slide

  74. TOOLS
    Tuesday, 13 November 12

    View Slide

  75. TOOLS
    What can we use to streamline this?
    Tuesday, 13 November 12

    View Slide

  76. OBJECTIFY
    Tuesday, 13 November 12

    View Slide

  77. OBJECTIFY
    Not strictly Hexagonal
    Tuesday, 13 November 12

    View Slide

  78. OBJECTIFY
    Not strictly Hexagonal
    Request Execution Framework
    Tuesday, 13 November 12

    View Slide

  79. OBJECTIFY
    Not strictly Hexagonal
    Request Execution Framework
    Dependency Injection aka Raptor
    Tuesday, 13 November 12

    View Slide

  80. class RequiresLoginPolicy
    def allowed?(current_user)
    !current_user.nil?
    end
    end
    class UnauthenticatedResponder
    def call(format, routes)
    format.any { redirect_to routes.login_url }
    end
    end
    MyApp::Application.routes.draw do
    objectify.defaults :policies => :requires_login
    objectify.policy_responders({
    :requires_login => :unauthenticated
    })
    objectify.resources :pictures
    end
    Tuesday, 13 November 12

    View Slide

  81. class PicturesCreateService
    def call(current_user, params)
    current_user.pictures.create params[:picture]
    end
    end
    class PicturesCreateResponder
    def call(service_result, format)
    if service_result.persisted?
    format.any { redirect_to service_result }
    else
    format.any { render template: "new.html.erb" }
    end
    end
    end
    Tuesday, 13 November 12

    View Slide

  82. MORPHINE
    Tuesday, 13 November 12

    View Slide

  83. MORPHINE
    Lightweight DI tool
    Tuesday, 13 November 12

    View Slide

  84. class Application
    include Morphine
    register :service do
    Service.new client
    end
    def perform_action(client)
    service.authorize!
    end
    end
    Tuesday, 13 November 12

    View Slide

  85. describe ‘test’ do
    let(:client) { double ‘client’ }
    let(:service) { double ‘service’ }
    let(:application) { Application.new }
    before { application.service = service }
    subject { application.perform_action client }
    specify “behaviour”
    end
    class Controller
    def action
    Application.new.perform_action current_user
    end
    end
    Tuesday, 13 November 12

    View Slide

  86. PERPETUITY
    Tuesday, 13 November 12

    View Slide

  87. PERPETUITY
    DataMapper implementation
    Tuesday, 13 November 12

    View Slide

  88. PERPETUITY
    DataMapper implementation
    Define mappers for PORO Objects
    Tuesday, 13 November 12

    View Slide

  89. PERPETUITY
    DataMapper implementation
    Define mappers for PORO Objects
    Would probably still need a wrapper
    Tuesday, 13 November 12

    View Slide

  90. class Article
    attr_accessor :title, :body
    end
    Perpetuity.generate_mapper_for Article do
    attribute :title, String
    attribute :body, String
    end
    article = Article.new
    article.title = 'New Article'
    article.body = 'This is an article.'
    Perpetuity[Article].insert article
    Tuesday, 13 November 12

    View Slide

  91. class Library
    def initialize(responder, store)
    @responder, @store = responder, store
    end
    attr_reader :responder, store
    def create(type,attrs)
    obj = type.new
    obj.title = attrs[:title]
    obj.body = attrs[:body]
    store.insert article
    responder.success
    end
    end
    store = Perpetuity[Article]
    lib = ArticleLibrary.new response, store,
    lib.create Article, params[:article]
    Tuesday, 13 November 12

    View Slide

  92. SUMMARY
    Tuesday, 13 November 12

    View Slide

  93. SUMMARY
    Push / Inject dependencies
    Tuesday, 13 November 12

    View Slide

  94. SUMMARY
    Push / Inject dependencies
    Give application logic ways to respond
    Tuesday, 13 November 12

    View Slide

  95. SUMMARY
    Push / Inject dependencies
    Give application logic ways to respond
    Isolate components
    Tuesday, 13 November 12

    View Slide

  96. SUMMARY
    Push / Inject dependencies
    Give application logic ways to respond
    Isolate components
    Define *your* API
    Tuesday, 13 November 12

    View Slide

  97. BENEFITS
    Tuesday, 13 November 12

    View Slide

  98. BENEFITS
    Cleaner code
    Tuesday, 13 November 12

    View Slide

  99. BENEFITS
    Cleaner code
    Faster tests
    Tuesday, 13 November 12

    View Slide

  100. BENEFITS
    Cleaner code
    Faster tests
    Easier to adapt
    Tuesday, 13 November 12

    View Slide

  101. THANKS
    Jon Rowe
    @jonrowe (Twitter, GitHub, etc)
    http://jonrowe.co.uk
    Tuesday, 13 November 12

    View Slide

  102. LINKS
    Tuesday, 13 November 12

    View Slide

  103. LINKS
    https://github.com/bitlove/objectify
    Tuesday, 13 November 12

    View Slide

  104. LINKS
    https://github.com/bitlove/objectify
    https://github.com/bkeepers/morphine
    Tuesday, 13 November 12

    View Slide

  105. LINKS
    https://github.com/bitlove/objectify
    https://github.com/bkeepers/morphine
    https://github.com/jgaskins/perpetuity
    Tuesday, 13 November 12

    View Slide

  106. LINKS
    https://github.com/bitlove/objectify
    https://github.com/bkeepers/morphine
    https://github.com/jgaskins/perpetuity
    Tuesday, 13 November 12

    View Slide

  107. LINKS
    Tuesday, 13 November 12

    View Slide

  108. LINKS
    http://weblog.therealadam.com/
    2011/10/09/your-frienemy-the-orm/
    Tuesday, 13 November 12

    View Slide

  109. LINKS
    http://weblog.therealadam.com/
    2011/10/09/your-frienemy-the-orm/
    http://rubyrogues.com/078-rr-
    hexagonal-rails-with-matt-wynne-and-
    kevin-rutherford/
    Tuesday, 13 November 12

    View Slide