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. ADVENTURES IN THE HEXAGONAL JON ROWE Tuesday, 13 November 12

  2. HEXAWAT? Tuesday, 13 November 12

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

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

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

    about? Why is the rum gone? Tuesday, 13 November 12
  6. HEXAGONAL Tuesday, 13 November 12

  7. HEXAGONAL Hexagonal Architecture Tuesday, 13 November 12

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

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

  10. HISTORY Originally by Alistair Cockburn Also called “Ports and Adaptors”

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

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

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

    => DB Tuesday, 13 November 12
  14. Application Tuesday, 13 November 12

  15. HEXAGONAL Tuesday, 13 November 12

  16. HEXAGONAL Decouple the application Tuesday, 13 November 12

  17. HEXAGONAL Decouple the application Make the business logic the focus

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

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

    Provide adaptors for technology Provide adaptors for I/O Tuesday, 13 November 12
  20. HEXAGONAL RAILS Tuesday, 13 November 12

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

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

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

    the application ... Rails is the HTTP Adaptor Tuesday, 13 November 12
  24. HEXAGONAL RAILS Describes building apps *with* Rails Tuesday, 13 November

    12
  25. Application Tuesday, 13 November 12

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

  27. WHY? Tuesday, 13 November 12

  28. WHY? Separate responsibilities Tuesday, 13 November 12

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

  30. WHY? Separate responsibilities Not just SOC Separate business logic from

    web logic Tuesday, 13 November 12
  31. WHY? Tuesday, 13 November 12

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

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

    November 12
  34. WHY? Push dependencies inwards Make things more explicit Easier to

    see coupling / dependancies Tuesday, 13 November 12
  35. WHY? Tuesday, 13 November 12

  36. WHY? Modular code Tuesday, 13 November 12

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

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

    13 November 12
  39. INTERLUDE Tuesday, 13 November 12

  40. HOW? Tuesday, 13 November 12

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

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

    13 November 12
  43. HOW? Dependency Injection / Inversion Pure Ruby service classes Define

    an API / Duck Typing Tuesday, 13 November 12
  44. CONTROLLERS Tuesday, 13 November 12

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

  46. CONTROLLERS Controller provides what is needed Controller provides how to

    respond Tuesday, 13 November 12
  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
  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
  49. RESPONDERS Tuesday, 13 November 12

  50. RESPONDERS Wrap response logic Tuesday, 13 November 12

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

    12
  52. RESPONDERS Wrap response logic Can be reused Can be stacked

    Tuesday, 13 November 12
  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
  54. CORE Tuesday, 13 November 12

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

    12
  56. CORE Do things without knowledge of Rails Respond back with

    results Tuesday, 13 November 12
  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
  58. DEPENDENCY INJECTION Tuesday, 13 November 12

  59. DEPENDENCY INJECTION Not scary Tuesday, 13 November 12

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

    13 November 12
  61. DEPENDENCY INJECTION Not scary Can be simple plain Ruby Pass

    in what you need Tuesday, 13 November 12
  62. SIDE NOTE Tuesday, 13 November 12

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

  64. SIDE NOTE Use Rack, Use Routes Provide info before the

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

    controller E.g. Warden / Rack env for users. Tuesday, 13 November 12
  66. PERSISTENCE Tuesday, 13 November 12

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

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

    12
  69. PERSISTENCE Not very talked about No consensus Still hard, how

    do we deal with AR? Tuesday, 13 November 12
  70. PERSISTENCE Tuesday, 13 November 12

  71. PERSISTENCE Wrap ORM API Tuesday, 13 November 12

  72. PERSISTENCE Wrap ORM API Create repositories that pass back objects

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

    Use DataMapper pattern? Tuesday, 13 November 12
  74. TOOLS Tuesday, 13 November 12

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

    November 12
  76. OBJECTIFY Tuesday, 13 November 12

  77. OBJECTIFY Not strictly Hexagonal Tuesday, 13 November 12

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

    12
  79. OBJECTIFY Not strictly Hexagonal Request Execution Framework Dependency Injection aka

    Raptor Tuesday, 13 November 12
  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
  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
  82. MORPHINE Tuesday, 13 November 12

  83. MORPHINE Lightweight DI tool Tuesday, 13 November 12

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

    def perform_action(client) service.authorize! end end Tuesday, 13 November 12
  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
  86. PERPETUITY Tuesday, 13 November 12

  87. PERPETUITY DataMapper implementation Tuesday, 13 November 12

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

    November 12
  89. PERPETUITY DataMapper implementation Define mappers for PORO Objects Would probably

    still need a wrapper Tuesday, 13 November 12
  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
  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
  92. SUMMARY Tuesday, 13 November 12

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

  94. SUMMARY Push / Inject dependencies Give application logic ways to

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

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

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

  98. BENEFITS Cleaner code Tuesday, 13 November 12

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

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

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

    November 12
  102. LINKS Tuesday, 13 November 12

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

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

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

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

  107. LINKS Tuesday, 13 November 12

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

  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