Sunday, January 1, 2012

DCI and Rails, lessons learnt

I first learnt about DCI almost one year ago. After some research and experiments, we decided to try it in production projects. Mostly in the GameBoxed project. 

My overall summary is that DCI is really awesome. It simplifies the way you think about architectures.  Keep in mind, that our research is still in progress, it's not always clear what's the best solution.

At GameBoxed we have one backend application (API) that runs many social games (CoffeeScript frontend applications). Each game can be different, but they share some common things together. The base object/class is a Player. In every game, though, the player can have different behavior. At the controllers level we have a filter that does the main specific-to-game extension, like:

current_player.extend(ItemCollecting::Player) 

or

current_player.extend(Monopoly::Player) 

I'm not saying that this is hugely better than using inheritance, but for me it's a "lighter" way of object design. We can now test Monopoly::Player totally on its own, without all the base player responsibilities.

At the level of the CoffeeScript applications we also use some DCI ideas, like constructing habits and use-cases (invite-friends, start-game, choose-prize). We're still in the process of finding the best design, but in my opinion the DCI way of thinking really helps in designing all of that.

DCI reduces the number of objects in your project, but increases the number of roles they play. For me, it's easier to understand and think this way. In our case (the social games) the main objects interacting with each other are: Player, Game, Prize, Friends. Many other things *could* be a role (it doesn't have to). In the monopoly game, the Board can just be a role of the game object. 

The idea of use-cases, habits (as explained in the "Lean architecture" book), can make your architecture more explicit.

It's not only roses, though. Here are some problems and my comments about the problems. 

1. If not all of your team is truly convinced to DCI, then don't start with that. Even if only one person in a team doesn't get DCI it can make it very hard. As with every "new" thing, it requires more education. Experiments are fine, but at some point the whole team needs to have a common "mental model" of the system you're working on. 

2. Using DCI at the level of ActiveRecord classes is wrong. We felt into this trap. DCI makes more sense at the OOP level. That's why I'm focusing so much about separating the two. One problem we've had was that, even though we extended some objects in "contexts", the same objects retrieved from AR again (without Identity Map) didn't have the roles. Thus the need for extending them again, deep in the models.

3. Extending objects with roles should be at the context level. We've had some extending at the model level (due to the lack of IdentityMap in Rails 3.0, now that we've upgraded to Rails 3.1 this problem disappears). This quickly leads to a mess. It's also a problem of using ActiveRecord "objects" with DCI. Use ActiveRecord only for persistence, not for domain logic.

4. DCI without contexts/use-cases is useless. We were not disciplined enough to have contexts everywhere and problems appeared quickly. I need to stress it more - DCI is not (only) about dynamically extended objects. It's much more than that. You need to start thinking in use cases.

Let me know if you're interested in other lessons from using DCI.