Adventures In The Hexagonal (ShRUG November 12)

D79fc498d7a5b2ce12180890247476f0?s=47 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

D79fc498d7a5b2ce12180890247476f0?s=128

Jon Rowe

November 12, 2012
Tweet

Transcript

  1. 4.
  2. 5.

    HEXAWAT? Who is this crazy person? What is he talking

    about? Why is the rum gone? Tuesday, 13 November 12
  3. 18.

    HEXAGONAL Decouple the application Make the business logic the focus

    Provide adaptors for technology Tuesday, 13 November 12
  4. 19.

    HEXAGONAL Decouple the application Make the business logic the focus

    Provide adaptors for technology Provide adaptors for I/O Tuesday, 13 November 12
  5. 23.

    HEXAGONAL RAILS Describes structure of application but... Rails is not

    the application ... Rails is the HTTP Adaptor Tuesday, 13 November 12
  6. 34.

    WHY? Push dependencies inwards Make things more explicit Easier to

    see coupling / dependancies Tuesday, 13 November 12
  7. 43.
  8. 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
  9. 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
  10. 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
  11. 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
  12. 61.

    DEPENDENCY INJECTION Not scary Can be simple plain Ruby Pass

    in what you need Tuesday, 13 November 12
  13. 64.

    SIDE NOTE Use Rack, Use Routes Provide info before the

    controller Tuesday, 13 November 12
  14. 65.

    SIDE NOTE Use Rack, Use Routes Provide info before the

    controller E.g. Warden / Rack env for users. Tuesday, 13 November 12
  15. 69.

    PERSISTENCE Not very talked about No consensus Still hard, how

    do we deal with AR? Tuesday, 13 November 12
  16. 73.

    PERSISTENCE Wrap ORM API Create repositories that pass back objects

    Use DataMapper pattern? Tuesday, 13 November 12
  17. 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
  18. 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
  19. 84.

    class Application include Morphine register :service do Service.new client end

    def perform_action(client) service.authorize! end end Tuesday, 13 November 12
  20. 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
  21. 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
  22. 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
  23. 95.

    SUMMARY Push / Inject dependencies Give application logic ways to

    respond Isolate components Tuesday, 13 November 12
  24. 96.

    SUMMARY Push / Inject dependencies Give application logic ways to

    respond Isolate components Define *your* API Tuesday, 13 November 12