You've seen complicated codebases. You've dug through other people's code without a clue to what's going on or how it all fits together. Perhaps it was your own code or perhaps someone else's but you know how difficult it can be to figure out how this or that aspect of a system implements some required feature.
Often, code organization is a key aspect of aiding communication. Today on the 37signals blog, David Heinemeier Hansson wrote about how ActiveSupport::Concern can really help clean up your code. Rails 4 will even have new concerns directories in the load path by default, making it even easier to use this feature.
He points out that Basecamp has almost 40 concerns that organize features required for multiple models. Giving an example, he says "This concern can then be mixed into all the models that are taggable and you’ll have a single place to update the logic and reason about it."
What's great about this is that single place. To understand something thats "Taggable" or "Searchable" you only need to find that concern and you have all that you need. Making changes is just as easy; it's all right there.
Addressing some of the objections to these fat models, he says:
It’s true that this will lead to a proliferation of methods on some objects, but that has never bothered me. I care about how I interact with my code base through the source. That concerns happen to mix it all together into a big model under the hood is irrelevant to the understanding of the domain model.
I agree with the first part of this. Most of the time an object that has many unrelated methods isn't much of a concern. But when we need to drop into a debugger and look at what the object is and can do, then we are met with far more than we need. Inspecting the runtime object becomes difficult because of the overwhelming features and responsibilities that it has.
Almost 100% of the time, however, we interact with code through the source. This is spot on. We read and write code. But the fact that concerns happen to mix it all together under the hood is not irrelevant to understanding a domain model. It's important how many of these mixins are used and how complicated an individual class can become.
But even more important is how it works. Not how things are mixed in, but how your program works. What is going on?
Why do you care that a model is Taggable? What purpose does it serve and what other actor in the system will have a need for it?
Using concerns can be helpful, but if it's used by many models it may effectively become a fragile base class problem where changes to it have unintended ripple effects through those models that inherit its features.
ActiveSupport::Concerns and Roles in DCI are not the same. Roles in DCI are ONLY defined within a context. Your context represents a use case for your system and is larger than a single procedure. A use case covers success paths, failure paths, and alternate paths to the execution.
I had one reader of Clean Ruby, Drew Ulmer, write to me and say:
As far as putting roles directly into contexts, I can't tell you how much I love this idea. It's fantastic to have all of the logic and model behavior for a specific activity in one place and not have to jump around files. It keeps the models focused around how they are defined and not around what we use them for or how they behave; it keeps the behavior focused and easy to reason about; and it defines a very clear API that must be implemented for a context.
I passed this along to my mailing list and after reading it, I got this response from Anthony Burton saying:
To be honest, reading this I kept thinking "that's just the way things were supposed to be". Nevertheless, I always get so frustrated trying to dig through a project (new or old, right?), and trying to trace the flow of execution and how things fit together. This made me think of a project I worked on a few years back (which thankfully had a substantial logging capability) where I wrote a little perl script that would parse logs files and show how things were linked together. It was a very popular utility with the other devs and I was always curious, 1) why no one had written something like it before me; and 2) why the code wasn't just organized differently.
This is a valuable lesson for communicating what your software is supposed to do. You not only need to read the code, but you need to understand how different pieces work together. DCI is a great way to organize code and achieve better communication.
Here's a dumb example of what your context might look like:
class SwappingDetails def initialize(source, recipient) @source, @recipient = source.extend(Source), recipient.extend(Recipent) end def swap # trigger the appropriate methods end def other_trigger # trigger alternate behavior end module Source # some related methods here end module Recipent # some related methods here end end
Those modules represent the methods required for an object playing the role. With concerns, you must understand more about the classes of objects ahead of time whereas within a DCI context you'd focus only on roles and their interaction. When using concerns, you understand less about what will trigger the action, what other objects are necessary, and what roles those other objects play.
Concerns can be useful, but just like anything else they can be done poorly. Thinking in terms of interacting roles is not the same as abstracting related behaviors into a module.
Regardless of what you do in your application, communicating it's purpose is key to managing change. The better others can understand how something works, the easier it will be to make necessary changes.