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.

5 comments:

Oriol said...

Really interesting post, I'd love to know more about how you play with AR and separate it from the actors and roles.

Andrzej Krzywda said...

My last post touches this topic of domain/persistence:

http://andrzejonsoftware.blogspot.com/2011/12/rails-is-still-cool.html

I'm going to write more about it soon.

Pavel Mitin said...

I am sure, we'll see a lot of DCI-based rails projects in the near future. But at the moment lessons learned during a real production project are very valuable. Thank you for sharing your findings.

Question: How do you implement associations between objects/roles. I understand, that the answer depends on whether activerecord objects moved to the separate persistent layer or not. So, please elaborate both variants. To clarify my question, I would "guess" :) a few possible answers, assuming activerecord objects are used as domain objects:
1) An activerecord class uses "out of the box" associations (belongs_to, has_many...). The class contains all associations, what are needed for roles, depended on the class.
2) The activerecord class doesn't declare associations at all (although it may contain foreign keys). Roles declare associations.

Thanks in advance.

Andrzej Krzywda said...

@Pavel

When I experimented with DCI inside AR objects I went with the second way. It felt better that the first way, but it was still "wrong" because the roles included the associations at the class level, not at the object level (just a limitation of ActiveRecord that I was not able to work around).

If I had to do DCI within AR, then I would do it this way again. There is however some hope in Rails 4, where ActiveRecord can be a module/role. It will make it better, but you should keep in mind that ActiveRecord should not dictate your OOP design and that would still be the case here.

kyktommy said...

i would like to see more practical examples of using DCI