Skip to content

Instantly share code, notes, and snippets.

@mattwynne
Forked from gmoeck/some_controller.rb
Created July 11, 2012 08:14
Show Gist options
  • Save mattwynne/3088890 to your computer and use it in GitHub Desktop.
Save mattwynne/3088890 to your computer and use it in GitHub Desktop.
class CommentsController < ApplicationController
def create
app.create_comment(params)
end
private
def app
MyRailsApp.new
end
end
# this is a facade over the domain which wires up the right adapters and the right domain objects
class MyRailsApp
def create_comment(params)
comment = Comment.new(params)
comment_processor.new_comment(comment)
end
private
def comment_processor
listener = FanoutAnnouncer.new(twitter_poster, facebook_poster, comment_persister)
CommentProcessor.new(listener)
end
def twitter_poster
TwitterPoster.new
end
def facebook_poster
FacebookPoster.new
end
def comment_persister
SQLCommentPersister.new
end
end
# example class
class CommentProcessor < Struct.new(:listener)
def new_comment(comment)
new_comment = comment.with_language(language_for(comment.body))
.with_spam(is_spam(comment.body))
listener.announce('new_comment', new_comment)
end
end
# immutable value object for carrying data about a comment
class Comment
include Virtus::ValueObject
attribute :body, String
def with_language
# ...
end
end
@mattwynne
Copy link
Author

There are a few changes in here from Greg's original:

  1. I've moved most of the code out of the controller into a MyRailsApp class. I like this because it keeps the controllers thin, but gives me a class with an interface that still speaks the language of the Rails app, where I can re-use things like the comment_processor method, if I need to use that to service other controller actions.
  2. Instead of using a Translator class, I'm doing the adapting in the MyRailsApp, in this case mapping the request params hash straight into attributes of a Comment value object. If the mapping weren't so simple (in this totally invented example!) this might need an adapter object of it's own.
  3. I've changed the wiring of the CommentsProcessor so that it only knows about a single listener, but we have a separate FanoutAnnouncer class that will forward those messages on to multiple listeners.

@gmoeck
Copy link

gmoeck commented Jul 11, 2012

I think I agree with most of this. My only concern is with the FanoutAnnouncer. It seems like the comment processor's responsibility to publish events about itself, and those events should be distinct from events coming from other objects. In my experience when you have something like a FanoutAnnouncer it inevitably ends up getting passed into multiple objects, which means it gets treated like an EventBus. I've found that this hides dependencies and tends to obscure to communication patterns between objects personally.

Also, would you test your facade? I would asume it's just there to do the wiring and shouldn't have any logic itself (the equivalent of main essentially). People might miss that at a first glance.

@mattwynne
Copy link
Author

You have to admit that the code in the CommentProcessor is simpler though? I'm not suggesting you use that instance for other objects, just that it owns the job of managing who is listening to events from the processor and fanning them out. I built something like this in Cucumber a long, long time ago.[1]

I don't test the facade, no. I'm still playing about with this idea, but I personally like it better than a bunch of use-case objects as my 'route in'. It keeps the controllers so simple there's almost nothing left.

[1] https://github.com/cucumber/cucumber/blob/master/lib/cucumber/broadcaster.rb

@gmoeck
Copy link

gmoeck commented Jul 12, 2012

Yes, I certainly agree that it makes the comment processor simpler, although I wonder if it lessens its cohesion. In order to send a message to the process I need one instance, and in order to subscribe to its updates I need another. I guess so long as you didn't need to be doing dynamic subscription/unsubscription outside of factories it wouldn't matter, which would probably be the case in all server side apps.

I think we're largely on the same page.

@mattwynne
Copy link
Author

mattwynne commented Jul 13, 2012 via email

@nicholasjhenry
Copy link

Just a small detail, but was interested if you would pass in the entire params hash into the facade or filter on the comment attributes. The example never specifies the comment attributes; i.e. params[:comment].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment