RSS Feed
Apr 10

DCI (Data-Context-Interaction) – what is it, and when should I use it?

Posted on Tuesday, April 10, 2012 in Agile, Professional, Ruby on Rails

If you’re a hardcore, 10-hours-a-day Rails developer, you already know about DCI. For the rest of you, here’s a gentle introduction.

Rails developers (sorry, I just can’t force myself to use the word “Rubyists”) are an interesting lot. They care deeply about design and architecture. Deeply. In terms of application characteristics, they have a place at the head table alongside things normal folks care about – things like stability and functional fitness for duty.

So, while most of the world has been contentedly adhering to design principles and patterns like MVC and Model-Driven Design, Rails developers have been searching for The Next Big Thing. Some people feel like MVC drives a person to include too much code in a controller. Others tend to worry about too much code in models.

Before introducing DCI, I’ll offer an opinion of it and the role it should play in your bag of design tools. DCI is a sound, common-sense design solution – a way to keep an application’s implementation sane (full of concise, loosely-coupled, easily-understood classes) when scope and complexity might begin to be a challenge in a straight-up MVC design. That said, if you run across a colleague in the hallway who emphatically claims that DCI ought to be the new way all Rails applications are built, just give him a simple smile, pat him on the head, and move along, taking heart in your more measured and wise view of all things Rails.

Now then. DCI. Data-Context-Interaction. (Good acronyms, BTW, always use three letters.) The core concept of DCI is a recognition that entities – in Rails, models – do not exhibit a static set of behaviors or responsibilities. Rather, those behaviors and responsibilities depend on the context in which the entity is acting. Consider a model instance you’re probably most familiar with. You. If you were a Rails model, you might have the following method definitions in your internal wiring:

def wake
def wash
def brush
def pour
def drink
def chew
def swallow
def read
def walk
def board_train
def sit
def exit_train
def explain_dci_to_colleague
def think_abstractly
def write_test
def write_code
def run_tests

You get the picture. You are a complex example of a model. You do thousands, perhaps millions of things (I guess that number depends on the degree of abstraction in your “things” taxonomy, and perhaps on the number of hours a day you sit in front of the TV). If one were to include the code that implements all of these methods in one class, you’d have a pretty ugly, monolithic implementation.

How then, do we begin to decompose such complex model behavior into a set of cohesive, loosely-coupled, easily-understood classes? If you ponder the above list of behaviors long enough, you’ll notice something interesting. Certain behaviors have affinities with one another. Wake, wash, brush – the morning routine. Pour, drink, chew and swallow? Obvious. Walk, board_train, sit, exit_train? Commuting. The remainder? Software engineering, of course.

In DCI, we formally recognize that behaviors often belong to roles that a model object can play, and that those roles are always played within one or more contexts. In our little example, the “Eater” role might be played in three contexts each day (breakfast, lunch and dinner). We might, then, move all of the behavior related to food and beverage consumption into a class that represents the “Eater” role, the software design and development behavior into a “Software Engineer” class, etc.

If you’re with me so far, and you really do implement the “Software Engineer” behavior, you might have a forward-leaning question in your head about now: “If we move role-specific behavior out of the model class, how do we access it when we need it?” Great question. This is where dynamic languages like Ruby come to the rescue.

Instead of moving our role-specific behavior into separate classes, we can move the behavior into modules. Modules that can be “mixed in” at runtime using the extends keyword to put all that wonderful behavior where it belongs – on our Person class – but only when it really needs to be there.

Which brings us to the ‘C’ in DCI – Context. A Person needs to behave as an Eater only in the Context of a meal. A Person needs to behave as a Software Engineer only in the context of Development Project. In DCI, we formalize these contexts as classes, which see to the extension of models with appropriate roles, and then invoke the appropriate behaviors. In addition to simply adorning models with context-specific behavior, contexts can also orchestrate behavior of related models (people and trains, for example). For Gang of Four pattern afficionados, a Context looks a bit like a Command – there are expectations as to its structure (its constructor triggers its implemented behavior, for example), but it’s free to do anything it wants within that behavior. In that sense, this is a Structural pattern (as opposed to a Behavioral pattern).

One nice side effect of the full DCI pattern is that models are extended within the scope of a context; when the work of that context is done, that scope is exited, and all model instances discarded. This means there is no need to worry about removing temporarily-assigned behavior from model classes that might be used later – they are born and die within the cozy confines of a context.

If you want to go deeper and take a look at a simple example complete with Ruby code, Mike Pack has written an informative blog post that does just that. Just remember as you go that DCI, like any design pattern, is but a tool in your kit, and that one must always remember the Agile advice to “do the simplest thing that works.” In any Rails application, there are likely to be models that are used very simply (basic CRUD operations). Basic MVC and direct implementation of behavior on those models is perfectly adequate. Those models that represent the “crown jewels” of your application, on the other hand, are likely to exhibit complex behavior, and DCI might be a great way to wrestle that complexity to the ground.

Apr 12

On Shimmer and Patience

Posted on Sunday, April 12, 2009 in Agile

Remember Shimmer on Saturday Night Live? Dan Aykroyd and Gilda Radner? “It’s a dessert topping and a floor wax!” If you’re as old as I am or enough of an SNL fan to have caught that old episode on DVD, you’re probably chuckling a little. Humor has many triggers in the human brain, and absurdity is certainly one of them.

That’s why my initial reaction to something that happened at work the other day was to laugh – a reaction which, thankfully, I was successful in suppressing. Not only because laughing would have been unprofessional, but because, as is often the case, my first impression was superficial and, ultimately, off the mark.

The topic of discussion was the Continuous Integration build at a client. Let’s see if I can recreate the scene. Names, of course, have been changed to protect those who may not want their names to appear here…

Jeff the Consultant: I see that there are 800-odd unit tests in our code base, and I can run them locally, but the CI build only runs 150 or so of them. Is something wrong?

Bob the Builder: No, this is correct.

Jeff the Consultant: Come again?

Bob the Builder: Yes, we do this intentionally.

Jeff the Consultant: Okay, I have to ask. Why? Does it take too long to run all of the tests?

Bob the Builder: No, not really. The build with all the tests runs in 10 minutes or so.

Jeff the Consultant: Then why do we run a just a subset of them on code checkin?

Bob the Builder: Well, we run the full set of tests in a different build that runs nightly.

Jeff the Consultant: Hmm… Doesn’t running the tests at night make it difficult to track down whose code submission is responsible for tests that start failing? And doesn’t it remove the benefit of immediate feedback? I’m getting on in years, and sometimes I get to work in the morning not remembering a lot about what I did the night before.

Bob the Builder: I see your point. It would be a lot better if we ran the full set of tests all the time, on every code check-in.

Jeff the Consultant: Great. So you’ll make the necessary changes to the build script?

Bob the Builder: No, I can’t do that.

Jeff the Consultant: Wonderful, thanks… wait – did you just say “No?”

Bob the Builder: Correct. I can’t change the build.

Jeff the Consultant: Okay, I have to ask. Why not?

Bob the Builder: Because we don’t want this build to fail.

Jeff the Consultant: (This is about the point where I had to start physically suppressing laughter) But the build is supposed to fail if there are broken tests. That’s how we know there’s something that needs to be fixed, and who’s responsible – er, best qualified to fix it.

Bob the Builder: Right, I understand that. But the jars we produce in this build get deployed in an overnight process. So we don’t want the build to fail, because if it does, the daily build won’t get deployed.

Jeff the Consultant: Okay, I see that. But why do you want to deploy a build that you know has bugs in it? Or, in this case, that might have bugs in it, since you’re not running all of the tests…

Bob the Builder: Well, the deployment isn’t just for this team. There’s another team on the East Coast that needs that build to be deployed every day.

Jeff the Consultant: Hmm… interesting. Are the tests we’re not running our tests or theirs?

Bob the Builder: Mostly ours, I think. But it doesn’t matter – we can’t let broken tests stop the deployment, because usually only a small number of features are affected by those tests, and lots of people need to see the deployed application the next day in our demo environment.

Jeff the Consultant: Okay, that makes sense – I guess. But isn’t there another way to do this? For example, couldn’t we run the full suite of unit tests at code check-in time, and make the nightly build the one that skips tests and reliably produces the demo jar files? That way at least we’d get back the benefit of immediate feedback and have a better idea whose code is causing a test to start failing.

Bob the Builder: I think we could do that. But I don’t have time to dig into an overhaul of the build process, because I have development responsibilities too, and we’re only 2 days from code freeze.

(Exit cubicle, stage right…)

By this time my head was spinning. I wasn’t sure whether what I’d just heard made sense, or whether I’d just been had. I was pretty sure the whole build arrangement they had settled on was crazy, but I could also see how an organization could have evolved into this arrangement because of conflicting needs.

The root of their problem is the Shimmer conundrum. It’s neither a dessert topping nor a floor wax, but their CI build is certainly trying to be two very different things at the same time. It was probably started as a CI build in the Agile sense, with the primary purpose being immediate feedback in the form of unit test results.

But of course, like any build, it also builds something. Those jar files were ripe for the picking, and some enterprising person had the great idea to deploy them every day so interested parties in the company would be able to see the application “fresh from the oven” – with all of the evolving new features on display.

The problem is that the secondary purpose of this particular build – generation of compiled, deployable software, became more critical to somebody – some person with influence – and the build began to slowly stop meeting its initial purpose.

So, is there a point to this rambling blog post? Indeed there is. It’s a word of caution to those of us in the consulting profession. It’s very easy for those of us who spend a lot of time reading and in other ways learning about “ideal” situations – such as well-run Agile projects – to become easily frustrated when we encounter things that are in some way less than ideal. We have to remember to suspend judgment long enough to understand the big picture – all of the various problems our clients are trying to juggle – and then offer our assistance in helping improve things, at a pace that makes sense in their environment.

I’ll try to remember this myself as I return to work tomorrow…