-
-
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 |
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
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.
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].
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.