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?
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.
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.
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.
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.
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.
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!
@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.