When to use Decorator vs Form Object vs Service Object?

797 views
Skip to first unread message

Samnang Chhun

unread,
Sep 14, 2013, 11:37:48 PM9/14/13
to objects-...@googlegroups.com
Suppose we have CommentController and whenever a user create a comment we post a notification to their facebook's wall as well. I refactor from using after_save callback to another approach.
There are different approaches like Decorator, Form Object, or Service Object. I'm stuck when to choose one beside the others and what context they are intended to be used?

Here I put different implementations of the example: https://gist.github.com/samnang/6567768 

Thanks,
Samnang

Matthew Rudy Jacobs

unread,
Sep 15, 2013, 9:41:16 AM9/15/13
to objects-...@googlegroups.com

Looking at your examples, I think none of them quite separate the two concerns.

There are two things we're trying to do here;

1. create a comment
2. notify Facebook if this is successful

and these should be separate.

--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Yevgeniy Viktorov

unread,
Sep 15, 2013, 12:11:47 PM9/15/13
to objects-...@googlegroups.com

In that particular case decorator looks, IMHO, nicer.
btw the problem looks quite similar to the example in "7. Extract Decorators" pattern from
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

And as Bryan (author of that post) says:

Decorators differ from Service Objects because they layer on responsibilities to existing interfaces. Once decorated, collaborators just treat the FacebookCommentNotifier instance as if it were a Comment

Samnang Chhun

unread,
Sep 15, 2013, 12:16:06 PM9/15/13
to objects-...@googlegroups.com
Yevgeniy, I took the example from Code Climate with Decorator implementation.


--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Kris Leech

unread,
Sep 16, 2013, 5:53:07 AM9/16/13
to objects-...@googlegroups.com
Matthew Rudy Jacobs wrote:
>
> Looking at your examples, I think none of them quite separate the two
> concerns.
>
> There are two things we're trying to do here;
>
> 1. create a comment
> 2. notify Facebook if this is successful
>
> and these should be separate.
>
I respectively disagree. You can create a comment elsewhere without also
posting to Facebook (since AR callbacks are not used).

Using one of the techniques described the Facebook part is not coupled
to Comment creation globally only within a localised context.

IMHO this is practical and provides encapsulation of a process ("Posting
Comment AND notifying Facebook").

Kris Leech

unread,
Sep 16, 2013, 6:01:39 AM9/16/13
to objects-...@googlegroups.com
The form object doesn't feel right to me, because of its name, if it was
called `CommentNotifier` instead then it would make more sense (to me).
In other words it would be a service object which just so happens to be
ActiveModel / `form_for` compliant. Or the Form part was nested within a
larger service object: https://gist.github.com/krisleech/6578764

Matthew Rudy Jacobs

unread,
Sep 16, 2013, 7:07:07 AM9/16/13
to objects-...@googlegroups.com
On 16 September 2013 10:53, Kris Leech <kris....@gmail.com> wrote:

I respectively disagree. You can create a comment elsewhere without also posting to Facebook (since AR callbacks are not used).

Using one of the techniques described the Facebook part is not coupled to Comment creation globally only within a localised context.

My general approach to something like this is create 2 objects;

1. a CommentCreator
2. a FacebookCommentNotifier

These 2 objects should do exactly what they say,
and in the context where we want to use them together I would define the "success" callback of the CommentCreator to instantiate and call the FacebookCommentNotifier
 

Samnang Chhun

unread,
Sep 16, 2013, 8:15:37 AM9/16/13
to objects-...@googlegroups.com
From the 3 implementations I like Decorator and Service Object because creating comment is straightforward, so we don't need another object to handle it.
  • Decorator: FacebookCommentNotifier make sense to me because its responsibility just posting to wall on facebook.
  • Service: CommentNotifier has responsibility to encapsulate a complex process for creating a comment and post to wall on facebook, but I don't like its name because it doesn't imply the creating a comment as well. 


--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Hugh

unread,
Sep 16, 2013, 10:16:15 AM9/16/13
to objects-...@googlegroups.com
It's probably not a 'rule' but generally I lean towards decorators for outbound actions like index and show and service objects for inbound actions like update and create.

Mike Moore

unread,
Sep 16, 2013, 1:37:39 PM9/16/13
to objects-...@googlegroups.com
TL;DR: Use them when the pain they solve is worth more than the cost of maintaining them.

The OP's question is "when" to use these various approaches. I think the indirection added by the three code examples in the gist make the controller code _less_ readable. They are reducing only _one line_ of code in the controller action by introducing a lot of complexity somewhere else in the app. Here is what the controller would look like with the ActiveRecord callback removed.

    class CommentsController < ApplicationController
      def create
        @comment = Comment.new params[:comment]
        if @comment.save
          Facebook.post title: @comment.title, user: @comment.author
          redirect_to blog_path, notice: "Your comment was posted."
        else
          render "new"
        end
      end
    end

This seems simple and direct to me. I don't understand the pain that is addressing by adding decorators or form objects or service objects. (Facebook is already a service object AFAICT.) We know from looking at this controller action that comments created from this controller will get posted to Facebook. Comments created in other parts of the application may not post to Facebook. The question at hand is how the controller is responsible for making the post to Facebook, either by making the call itself or by calling an object to do it as part of the response to the request. I suppose this coupling is what you are trying to avoid by adding these new objects. The controller needs to know that a Facebook post will be made, but you don't want the controller to be responsible for actually making the post.

To me, this class of problem is opposite of what Decorators and Presenters are useful for. We aren't communicating the state of the domain to the View Layer, we are processing user actions. Perhaps a different abstraction would be useful for thinking about this. Matt Wynne's Hexagonal Rails presentation inverted the control flow like following:

    class CommentCreator
      def initialize listener
        @listener = listener
      end

      def perform params
        comment = Comment.new params[:comment]
        if comment.save
          @listener.comment_create_succeeded(comment)
        else
          @listener.comment_create_succeeded(comment)
        end
      end
    end

    class CommentsController < ApplicationController
      def create
        CommentCreator.new(self).perform params[:comment]
      end

      def comment_create_succeeded comment
        redirect_to blog_path, notice: "Your comment was posted."
      end

      def comment_create_succeeded comment
        @comment = comment
        render "new"
      end
    end

The only change needed to also have it post to Facebook is injecting one more object into the chain of command in the controller's create method.

    class FacebookCommentPoster
      def initialize listener
        @listener = listener
      end

      def comment_create_succeeded comment
        Facebook.post title: comment.title, user: comment.author
        @listener.comment_create_succeeded comment
      end

      def comment_create_succeeded comment
        @listener.comment_create_succeeded comment
      end
    end

    class CommentsController < ApplicationController
      def create
        CommentCreator.new(FacebookCommentPoster.new(self)).perform params[:comment]
      end
    end

My hope in showing you this is that it looks foreign to you. If not, please work with me and pretend it looks foreign. This is a radically different approach than the Form Object or Service Object, but the end results are the same. How much less readable is this than the refactored controller action? Its more indirect, but is it more readable or maintainable? The question is whether this is worth it, and I don't think it is. There are marginal improvements to testability, but it is much harder to follow. While I love Matt's approach, and Form and Service objects, I would need to solve a HUGE pain in order to use them. And I just don't see the pain in the original refactored code to justify this or the other approaches. In fact, I rarely see Rails apps with that level of pain.

I explain my view of pain driven development and how it relates to using presenters and decorators here: http://blowmage.com/2012/06/13/presenters-and-decorators-a-code-tour


--

Mike Kelly

unread,
Sep 16, 2013, 2:05:20 PM9/16/13
to objects-...@googlegroups.com

The main reason the hexagonal stuff is harder to follow is because (in rails) a controller has the responsibility of handling many different types of request for both the collection and items. If instead each HTTP method for every URL pattern in your app had its own handler object then the flow control would be obvious.

Rails defaults are making that painful, not hexagonal architecture.

Cheers,
M

Mike Moore

unread,
Sep 16, 2013, 2:15:44 PM9/16/13
to objects-...@googlegroups.com
On Mon, Sep 16, 2013 at 12:05 PM, Mike Kelly <mikeke...@gmail.com> wrote:

The main reason the hexagonal stuff is harder to follow is because (in rails) a controller has the responsibility of handling many different types of request for both the collection and items. If instead each HTTP method for every URL pattern in your app had its own handler object then the flow control would be obvious.

Rails defaults are making that painful, not hexagonal architecture.


I don't find hexagonal rails hard to follow myself, but others do (or most do). My point is there is a cost to varying from the Rails norms, and the question isn't which approach to use, but whether to use them at all. The simple examples don't have enough pain to solve to justify the approach. If you have a real app with real pain, the pain will guide you. Don't go shopping for a design pattern for the sake of using a design pattern, solve real pain.

May I also recommend Marcel Molina's Beautiful Code presentation? He specifically addresses the appropriateness of using patterns.

Mike Kelly

unread,
Sep 16, 2013, 3:29:23 PM9/16/13
to objects-...@googlegroups.com
Ok, in which case I got the wrong end of the stick! :)

Cheers,
M
> --
> You received this message because you are subscribed to the Google Groups
> "Objects on Rails" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to objects-on-rai...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.



--
Mike

http://twitter.com/mikekelly85
http://github.com/mikekelly
http://linkedin.com/in/mikekelly123

Matt Wynne

unread,
Sep 16, 2013, 7:12:12 PM9/16/13
to objects-...@googlegroups.com
On 16 Sep 2013, at 20:15, Mike Moore <blow...@gmail.com> wrote:

If you have a real app with real pain, the pain will guide you. Don't go shopping for a design pattern for the sake of using a design pattern, solve real pain.

From my experience, by the time you notice the pain you're already fucked.

It's going to take a lot of experience to have the acute sensitivity to know when there's a minor ache now that will be killing you in a few months time. I think it's a good idea to "waste" a bit of time playing with these patterns even on easy stuff, because then when you really need it, you'll already have practiced applying it.

Even if the business do have to wait a little longer for their next feature. Help them learn a little patience.


Kris Leech

unread,
Sep 17, 2013, 4:17:37 AM9/17/13
to objects-...@googlegroups.com
Mike Moore wrote:
> TL;DR: Use them when the pain they solve is worth more than the cost
> of maintaining them.
>
> The OP's question is "when" to use these various approaches. I think
> the indirection added by the three code examples in the gist make the
> controller code _less_ readable. They are reducing only _one line_ of
> code in the controller action by introducing a lot of complexity
> somewhere else in the app. Here is what the controller would look like
> with the ActiveRecord callback removed.
Very good point, with a disclaimer, sometimes you know that a certain
bit of code is going to be painful later. You might call this premature
optimisation. Its definitely a judgment call.

Personally in all cases where I would use an ActiveRecord `after`
callback or put anything other than a few lines in a controller I opt
for a service object instead. I find this easier to understand and can
jump from the controller to a service object in my editor. It feels
better, the more black boxes the better.

Kris Leech

unread,
Sep 17, 2013, 4:25:32 AM9/17/13
to objects-...@googlegroups.com
Mike Moore wrote:
> To me, this class of problem is opposite of what Decorators and
> Presenters are useful for. We aren't communicating the state of the
> domain to the View Layer, we are processing user actions. Perhaps a
> different abstraction would be useful for thinking about this. Matt
> Wynne's Hexagonal Rails presentation inverted the control flow like
> following:
I think decorator in this context is not view related, it is decorating
the saving of a comment with extra functionality. Presenter on the other
hand to me always makes me think view, probably because that is the
presentation layer.

Samnang Chhun

unread,
Sep 17, 2013, 10:18:52 AM9/17/13
to objects-...@googlegroups.com
I think decorator in this context is not view related, it is decorating the saving of a comment with extra functionality

I agree decorator here is used for adding functionality to existing object and keep the same interface from original object. 


--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rails+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.

Andrew Premdas

unread,
Sep 17, 2013, 10:29:08 AM9/17/13
to objects-...@googlegroups.com
On 17 September 2013 15:18, Samnang Chhun <samnan...@gmail.com> wrote:
I think decorator in this context is not view related, it is decorating the saving of a comment with extra functionality

I agree decorator here is used for adding functionality to existing object and keep the same interface from original object. 


But actually in this context it can do more, it can hide all the ActiveRecord cruft, and present a minimal method footprint for the controller to interact with. So in this context its better for this wrapper to not be a decorator (in the classic sense). This allows you to establish a pattern where its a smell for controllers to interact directly with model objects, which IMO is a very good thing, because the method signatures of model objects are just to large to be exposed in controllers.


 
On Tue, Sep 17, 2013 at 3:25 PM, Kris Leech <kris....@gmail.com> wrote:
Mike Moore wrote:
To me, this class of problem is opposite of what Decorators and Presenters are useful for. We aren't communicating the state of the domain to the View Layer, we are processing user actions. Perhaps a different abstraction would be useful for thinking about this. Matt Wynne's Hexagonal Rails presentation inverted the control flow like following:
I think decorator in this context is not view related, it is decorating the saving of a comment with extra functionality. Presenter on the other hand to me always makes me think view, probably because that is the presentation layer.


--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rails+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.



--
------------------------
Andrew Premdas
Reply all
Reply to author
Forward
0 new messages