Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Models, Roles, Decorators, and Interactions -- A modest proposal for a toned done version of DCI that isn't as janky as Concerns.

Models, Roles, Decorators, and Interactions

A modest alternative to DCI that might be worth further thought

One of the problems with advancing the discussion on DCI is that we lack a comparable alternative pattern that has the same goals, but favors a low ceremony approach. The closest thing we have to that is Rails concerns, but they are more like distant relatives of the DCI concepts rather than first cousins, and that makes comparisions between the two approaches not especially fruitful.

I am considering the idea of experimenting with my own paradigm that captures the intent and purity of DCI, but with the convenience of concerns. Please note that this is just the starting point of a conversation, it is NOT a promise of comercially available cold fusion or a cure for cancer. It's just a gist with an idea on it I'd like to hear your thoughts on.

What if we had a top-level topology that was split into Models, Roles, Decorators, and Interactions?

Models

A model is responsible for data access and persistence. It does not have logic of its own, apart from validations, and basic transformations that make its internal state more palatable for some other part of a system. Bonus points if these models are immutable, but it's not essential as long as consistency and safe access is maintained.

So for example, we might have a Person and Comment model, as shown below:

class Person < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :author, :class_name => "Person"
end

We might have some simple methods on these objects that make queries easier, but they wouldn't have any business logic in them. Models can be made up of other models via object composition, as long as those constraints were mainted.

Roles

A role is an object (potentially composite) that understands how to take specific actions that manipulate models, either directly or indirectly. It's not necessarily stateless, but its job is to collaborate with other objects, not to encapsulate data of its own. In that sense, a role is a transient thing that is meant to be constructed dynamically to complete an action, and then thrown out.

So in that sense, we might havve a Commenter role:

class Commenter
  def initialize(person)
    @person = person
  end
  
  def recent_comments
    @person.comments.where("created_at > ?", 3.days.ago)
  end
  
  def post_comment(text)
    @person.comments.create(:body => text)
  end
  
  def update_comment(new_text)
    raise "Comment not owned by this person" unless comment.author == @person
    
    comment.update_attribute(:body, new_text)
  end
end

This is a plain Ruby object that could perhaps live in app/roles. Like models, you can certainly build composite roles as needed, as long as their purpose is maintained.

Decorators

A decorator is an object that takes a model and transforms it into something suitable for consumption by some other part of the system. This is used sometimes for presentation purposes, other times it is used for adapting data so that it be passed from one kind of API to another. In any case, it is a simple transformation of a model into a different representation: nothing more, nothing less.

So for this example, we might have something like this:

class CommentDecorator
  def initialize(comment)
    @comment = comment
  end
  
  def to_json
    {:author => comment.author.name, 
     :body   => comment.body,
     :date   => comment.created_at }.to_json 
  end
  
  def to_html
    # use your imagination here
  end
end

Again, composition is fine here: CommentDecorator could be a trivial facade that rests on top of lots of specific decorators for individual formats, if it'd help make it more modular and maintainable.

Interactions

Here's where things get sticky. An interaction is not something that can be succinctly expressed in a general way, because it is by definition the code that forms the glue between the delivery mechanism (e.g. the Rails framework + the user's browser) and the application itself (the thing that understands the concept of a person, comment, and commenter). Because a generalization of interactions would be so abstract it would hardly mean anything at all, we must rely on common sense here. Complex interactions could be wrapped up in a network of objects, but we could work towards keeping interactions strictly procedural by pushing most of our substantial code into the Models, Decorators, and Roles.

That said, we could probably come up with plenty of sensible tactics and strategies for dealing with this part of our system, as long as we don't try to come up with the Grand Unified Theory of everything. By knowing what kind of application you want to build, and what kind of delivery mechanism you are using, it would probably be possible to build gems or libraries that help make certain kinds of interactions easier. However, rather than formalizing this concept, I'd prefer to leave it adhoc for now.

Sugar

I personally like writing explicit class definitions that are plain old Ruby. But we could probably justify sprucing up these constructs within the context of particular frameworks. So I'd have no problem with some rails magic that'd give us something like this for roles and decorators:

class Commenter < ActiveSupport::Role
  concerns :person # this is a reuse of the word only, not the semantics.
  
  def recent_comments
    person.comments.where("created_at > ?", 3.days.ago)
  end
  
  # ...
end

# usage
Commenter.new(some_person)

class CommentDecorator < ActiveSupport::Decorator
  decorates :comment
  
  # the view method and maybe a few other small helpers provided by decorator class
  
  def to_html
    view.content_tag(:p, comment.body)
  end
end

# usage
CommentDecorator.new(comment).to_html

I could care less whether this sugar used class inheritance or mixins, or if by some magic could be supported by composition (perhaps with a callback mechanism?). It's just an olive branch for those who love their sexy magic, but with the assumption that we can build it on top of a solid foundation.

What do you think?

It's just an idea, and since I just thought of it now, my code examples are as thin as the very DCI articles I've been critical of. I do plan to try out these ideas in a full scale application, but I'd like to get a sanity check on them first. They're not so much anything special or novel, they're just a collection of existing practices that I'm trying to group together so that we can discuss their interrelations. I'd love to hear what you think!

@practicingruby
Copy link
Author

practicingruby commented Dec 20, 2012

@norobotdiving: Thank you so much for the very thoughtful comment.

With MRDI, I'm just trying to put something out there for folks to try, and this post is on a gist for a reason: It's a transient request for feedback to help me figure out where to get started.

Your comment has solidified the feeling for me that the real solution to this problem will be to build (and contribute to) open source applications. This way, we can pick whatever paradigm we want to start with, and then use real use cases and problems to explore the costs and benefits of our design choices.

I've got a little boy who just learned how to crawl, and a business that's making just enough money for me to pay my bills at the end of the month. I can't promise that I'll be pushing the state of the art forward with open source apps, but I am going to try to find the time and energy to make it happen, or at least to help others get the ball rolling in 2013. Once we have real apps that are not behind an NDA or paywall that we can all talk about, I think the level of discourse will rise greatly.

@ericgj
Copy link

ericgj commented Dec 20, 2012

Thanks @sandal for putting this sketch down. At various times I've used compositional techniques, other times inheritance (e.g. Sequel's model Plugins, similar to Concerns I think), other times instance extend'ing (although never with a context object), to deal with "model interactions abstracted from persistence" -- for lack of a more concrete description of the problem domain -- which itself is part of the problem.

I kind of hesitate to contribute much to the discussion at the moment though, because I never really had a compelling case for using these techniques. I mean, they maybe contributed a bit to uncoupled domain logic and readability; or maybe they detracted from readability a bit for those used to traditional AR models; but it was basically a wash compared to vanilla MVC.

I think there is a lot of fetishism around techniques to solve problems very few people really have, and I don't really have time in my life for pursuing theoretical problems, I have enough real ones :O(. For domain logic especially, the primary question for me is do the interactions match the structure of what I'm hearing from the users about their domain ? The question of internal organization (including, yes, maintainability of the code) has to be weighed against the time it takes away from this.

So I fully agree with what @norobotdiving and @sandal are saying, we need some messy problems, preferrably with messy code to go with them, to motivate and clarify what these techniques are good for (or not).

A couple questions on 'MRDI' -

\1. Roles as described seems like a Facade over a single instance of a single class. But it seems like its usefulness is better explained in more complex cases, for instance:

  • instances of different classes taking on the Commenter role, or
  • a role providing an abstraction over several models at a time (or maybe that's what you mean by interaction?), or
  • a composite role, ie. a role that provides domain-specific ways of enumerating a collection of model instances (or even a collection of instances themselves wrapped in a specific role).

\2. When you say interactions are the code that forms the glue between the delivery mechanism ... and the application itself, that sounds to me more like the controller code, whereas from my limited understanding of DCI, 'interactions' are abstracted from the delivery mechanism and refer to things like transactions. And particularly refer to cases where you have several different strategies for a transaction, as described well (using composition) in Greg Moeck's article or solnic's -- from the last go-round of this discussion. Anyway, maybe you should address something of this understanding of what interactions are useful for, since that seems like the canonical kind of example.

@judofyr
Copy link

judofyr commented Dec 20, 2012

I like it.

I'd like to propose a small tweak to roles though:

class Person
  

  def commenter
    @commenter ||= Commenter.new(self)
  end
end

(This can be abstracted into a nice helper: role :commenter)

This has some some advantages:

  1. The lifetime of the role objects follows the lifetime of the model objects. You don't have to worry about where you should instantiate roles. You can also use state inside the role (for good or evil) since there's always a 1:1 mapping between role object and model object.
  2. It's easier for roles to access roles of other models.

This looks surprisingly similar to DCI:

  • We connect the models and roles inside the model, not the context.
  • Instead of dynamically extending models (person.update_comment) we have an extra indirection (person.commenter.update_comment).

I actually like the extra indirection since it makes it simpler to see where the method is located).

We're losing the possibility of polymorphism (?) though: In DCI you can have two roles that define a method with the same name so that calling model.foo can take two different paths depending on the context. I'm not sure if that's a good feature or not though…

@practicingruby
Copy link
Author

practicingruby commented Dec 20, 2012

Thanks for the reply @ericgj

\1. Roles as described seems like a Facade over a single instance of a single class. But it seems like its usefulness is better explained in more complex cases

Yep, all of the situations you described are where roles might actually make an important difference. I think we'll need to see how this conceptual mode supports those scenarios in realistic projects, but I don't see any barriers to them at a glance.

\2. When you say interactions are the code that forms the glue between the delivery mechanism ... and the application itself, that sounds to me more like the controller code, whereas from my limited understanding of DCI, 'interactions' are abstracted from the delivery mechanism and refer to things like transactions.

Yeah, I'm overloading a term here. IMO I feel like "interactions" are ill-defined by nature, and I worry about trying to make them into concrete concepts. Here, we may benefit from something like "Interaction patterns", which may be delivery-mechanism specific, or may be agnostic. This is very fuzzy, and as of right now that's why I prefer to leave it ad-hoc. If I envision carrying out these ideas in a Rails app, right now I'd consider my "interactions" to be everything in my controllers, views, rake tasks, etc. We'll see where that takes me and whether I find any higher level patterns to be valuable. I'd also be open to a new name here, to avoid confusion.

@practicingruby
Copy link
Author

practicingruby commented Dec 20, 2012

@judofyr What you've shown looks pretty reasonable to me, but I think it'd be a pattern that lives on top of this base organizational method rather than a direct part of it.

@Systho
Copy link

Systho commented Dec 20, 2012

I think that what you call "Interaction" really sounds like a "Use Case Controller" (being UI agnostic), or an "Action" to keep Rails vocabulary.

Most of the time we can have action methods in our controller thanks to their simplicity but we might want to promote those method to being Method Objects when they grow larger.

I like the idea of @judofyr to be able to have a Decorator from a Model but wouldn'it be nicer to obtain it from a Role ? And at the same time, wouldn't a role be a nice place to put factory methods accepting a param hash ?

I think the Decorator Pattern is becoming more and more used as a response-centric object but I sometimes feel there is a lack for a request-centric object. I think it would be nice to be able to construct a Role from params and then having that role being able to represent itself as a Decorator.

@practicingruby
Copy link
Author

practicingruby commented Dec 20, 2012

@Systho: Yeah, an "action" or "use case controller" sounds like it'd closely map to what I'm describing, although I'd prefer not to say that it is that thing, to avoid making the concept more specific than necessary as a starting point.

As for being able to have a role represent itself as a decorator, it's an interesting thought. We definitely do need figure out a way to tie all these things together: it's a bit awkard to instantiate many different (but related) objects just to fulfill a single request. But I think I'll just try to do it manually for now and see where the pain points are when I've had a chance to apply this approach a few times.

@randito
Copy link

randito commented Dec 20, 2012

DCI has a powerful allure to me because it hints at some way of managing the growing complexity of "business logic" at a place that makes sense. I feel like there is something powerful hiding under the surface of it, waiting to get out.

This post here (http://andrzejonsoftware.blogspot.com/2011/08/dci-patterns-how-to-write-dci-contexts.html) is especially tantalizing with potential.

Here's my take on it: https://gist.github.com/2626116

Unfortunately, once you look at actual code of people doing DCI (even in Ruby / Rails) it gets out of hand pretty quick.

@galtenberg
Copy link

galtenberg commented Dec 20, 2012

Having done DCI on rails 2 and 3 projects, I really think it's worthwhile to just revisit it. There's actually less involved than the scheme you propose, and the DCI way is more enjoyable than pattern-y classes.

Models: You're on the right track. Keep it basic, just about the thing itself (and its very immediate neighborhood).

Roles: Trust the mixin approach. It actually works really well to just extend a model object with additional use case-based methods, when called for (in contexts).

Context: This has a simple job, too. Extend model objects with roles, call role methods, verify outcome, return any results.

@ZombiDev did a really great job of showing how basic it is: http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby. (The reason his context does call/new is a DCI philosophical thing -- that it's best to start from the idea of live objects, real OO, rather than classes / class-orientation. The creators of DCI feel strongly about it, but due to the extra steps required in ruby, you can tailor to taste.)

I'm also not a fan of DCI meta-modules or helpers, as in the @randito gist above. There's no need for anything beyond basic ruby. That's the approach that will ultimately win -- DCI is a very simple pattern, it eschews the unnecessary.

But however you go, here's to joy and clarity. Thanks for pushing the conversation forward.

@verticonaut
Copy link

verticonaut commented Dec 20, 2012

The Decorator pattern does not 'transform' or 'adapt' a model. A decorator - as it says - decorates an object. Meaning that is can modify the existing API or extend it (see http://en.wikipedia.org/wiki/Decorator_pattern). You sample code does not implement a Decorator - rather a Formatter or Transformer. In order to implement a Decorator you would need to subclass from SimpleDelegator e.g. Then your implementation would extend the API - and the methods of the decorated comment would be still accessible.

E.g. Draper is an Gem to decorate you AR models. The models API ist still accessible, but may be modified. E.g. the accessor method updated_at would return a Time object, but the Decorator would overwrite the method so it would return a formatted Time object for use in a GUI. In addition there could be new extensions like e.g. edit_link that would render an edit link.

@practicingruby
Copy link
Author

practicingruby commented Dec 20, 2012

@galtenberg: Thanks for sharing your thoughts. The idea of me just revisiting DCI is actually part of the plan here, I only put forth MRDI because there are specific things about DCI that I do not understand (or am not convinced by) which I've been unable to get a clear answer to. (beware the link-bait title, the post is actually moderate in nature and the title is just satirical)

As for the issue of composition vs. mixins: Even if the latter work in practice, it seems to me as if we're almost creating a complex fix for the fundamental problem of inheritance, rather than sidestepping the issues entirely. I discuss this in a lot more detail over on the Reddit thread related to this proposal, and I would love to see what you think of the articles I link to in those comments. My fundamental opinion is that composition is conceptually simple and share-nothing by default, while mixins are fundamentally complex and share-everything by default. So in order to be convinced of the mixin approach, I want to see what value it adds that accounts for the extra cost in complexity.

As for contexts, I would love to see a nice example of them in practice, one that's deep enough to truly appreciate their value. The post from @zombiedev does not do that at all for me: it is far too simplistic for me to see the benefits.

If you've done DCI in the wild, I'd really love to see examples extracted from your work. Could you possibly share some of those?

@practicingruby
Copy link
Author

practicingruby commented Dec 20, 2012

@verticonaut Pretty much every word I've used here is potentially an overloaded term, and does not necessarily refer to some other thing that has been defined elsewhere. I'm open to renaming anything from this proposal.

When I say decorator, I mean it because these objects are meant to be used much in the same way that you might use Draper. I've just never personally seen the value in making full delegation automatic, and would actually count it as a special case (which should be avoided where possible) in the design style I'm advocating.

Thanks for pointing out the naming collision. I am mostly using MRDI not as a serious proposal for how we ought to do things, but instead as an experiment to help me understand (and perhaps help refine) the DCI concepts. If it ever did take on a life of its own, we'd need to think about these names much more carefully.

@lwoodson
Copy link

lwoodson commented Dec 22, 2012

Part of my complaint with DCI is that I think its naming and concepts are overly abstract, probably even poorly chosen and open to all sorts of misinterpretation. 'Context' ooohkay, whats a context? A business context, usually for carrying out a one or more business operations spanning multiple models. Okay, why not call it business operation or operation or services (http://martinfowler.com/eaaCatalog/serviceLayer.html)? 'Data', hmm, okay, sounds like a model. Why not call it model? Was there something in MVC that required models to be fat so that we need a different term to describe a skinny model? Not sure, okay, whatever. "Interaction", uh, yeah. Whats that? I'm not sure I know even now how to explain it. Lets say its the act of changing the way that a model may be interacted with in a given context. So a context-dictated change in the interface of an object. But I've yet to see any examples of such a change outside of the concept of a role and I can't really think of any. This company is a purchaser in this scenario and a seller in this scenario. A person is a felon in this scenario or a cop in this scenario. Why not simply call these roles?

In that regard, I like the spirit of your suggestion. Not sure if I agree with how all of that is accomplished.
Me personally, I wish it was called MRO or MRS (Model - Role - Operations|Services) and that as far as structuring within a rails project, that there were app/roles and app/operations or app/services directories.

@mech
Copy link

mech commented Jan 16, 2013

I think both has its place. Concern is good for use-cases like Taggable where every models has shared and similarly "change-together" piece of code.

When you comes to slightly complex business logic (which if you know business, its rules are ad-hoc and not tied down), you can use the DCI context to have its own isolated role-playing goodness.

Both has its place I feel.

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