Friday, February 11, 2011

DCI and Rails

I got really excited about DCI recently.  I'm still not sure if I get it right, but let me try to describe the way it works and how it can improve our Rails applications.

Here is the short version of this blog post:

#controller action 


current_user.extend Buyer 
current_user.add_to_cart(product_id, quantity)


DCI stands for Data Context Interaction. It was invented by Trygve Reenskaug, the same scientists who formulated MVC. It's a new paradigm, but it's very similar to OOP and fits especially well with the MVC architecture that we use in Rails applications.


Let me start with the evolution of Rails applications, so that I can explain current problems with the Rails model layer (in my opinion).

Very early we (the Rails community) agreed that it's better to move the logic down to models and keep controller very thin. I also wrote a blog post on how to write Rails controllers which is still my opinion on this topic.

As a result we had to cope with so called "fat models". This topic was an area of my research for years. It resulted in some techniques that I described in the "How to deal with fat models" blog post.

One of the things that I personally dislike is the overuse of Global State and Class Methods in Rails controllers. I have seen it everywhere and it my opinion in bigger apps it can result in maintenance problems. You can read why it's bad in this article by Hevery Misko. I must admit that with ActiveRecord it's very hard not to use Class Methods, but at least we can minimize it.

I was researching more OOP techniques that can be used to keep a nice OOP design in Rails model classes. I found the composite pattern (resulting in delegation) to be the closest to what I want to have. I asked the Ruby community why we don't use composition so often. The answers confirmed my observations that Ruby mixins (modules) are much more popular.

Anyway, it doesn't really matter - the result of "traditional usage" of mixins and composition in Ruby is very similar - you end up with a class which has lots of responsibilities. It doesn't matter that most of the responsibilities are delegated to other objects, actually it leads to a problem called self schizofrenia. This is especially visible in classes like User (in a social network app) or Article (in portal apps), which are very often a central place of application logic.

There are two techniques that I found useful during my research.

1. Current user pattern

This pattern was just reflecting the fact from a real life - a user interacts with our website so whenever a user does something there is a method in the User class responsible for that. Some methods can be grouped into "roles" and extracted to another class or a module, but they're declared in the User class.

2. Website pattern

This is the other side of the current user pattern. The user is interacting with a website, so it sounds like a good idea to have some kind of Website (or Application) class. This class encapsulates all the data and behaviour that is accessible from the website.


As you are probably seeing it now, it results in huge User and Website classes.

Just to be clear - both the current_user (in the Devise/Authlogic meaning) object and the website object are accessible from ApplicationController, so any controller could have access to them. The advantage here is that you don't need to introduce Global State and with the website pattern you can eliminate calls to class methods.

It was always kind of a smell to me that any controller takes the whole user object just to interact with a very small subset of the user interface. Let's say that you have a PostsController which needs to display all the posts. It takes the website object just to call website.posts, but theoretically it can call any other unrelated method. Static languages deal with it by communication using interfaces. It is a solution but it's still not perfect.

Here is where DCI comes in.

One of the things DCI claims is that most of the time we don't do object oriented programming but we do class oriented programming. It's more visible in the Java/.Net world but I think the same applies to most of us - Rails programmers. We put our declarations in classes and then instantiate objects with lots of unnecessary stuff.

DCI suggests that we keep our core model classes very thin. Zero logic, only data, if anything.


The logic/behaviour should be kept in roles. In Ruby we can nicely use mixins for that.


In an e-commerce app you would have User class but the buying/cart logic would be in the Buyer module. The User class knows nothing about cart, buying.

In a social network app you can create a GroupUser role and keep all the group related logic inside.

You can also extract some roles from the Website class. Some candidates in an e-commerce could be ProductRepository, OrderDepartment, NewsletterManager, Blog etc.

This logic is then injected runtime, at the object level, in the context of a specific use case.  In my opinion contexts or use case fits very well with the rails thin controllers rule.

Usually, one controller action is a specific use case. One example may be adding some product to a cart. The action could look like this:

current_user.extend Buyer
current_user.add_to_cart(product_id, quantity)

Another example, with a website:

class EventsController
  def index
    website.extend EventsBoard
    @events = website.recent_events
  end
end

As you see we use .extend method here which allows extending an object with a module. It all happens at the object level.

To me, this technique is most crucial for DCI (from a Rails perspective). Extending the object with a role at the controller level simply solves all of the OOP problems I had to deal with. The class doesn't know about all the roles, there's no problem with huge classes or object schizofrenia. You just create roles which are usually small modules and that's it.

DCI suggests that we keep use cases as classes. If my understanding is correct the place for creating use cases would be at the controller level. Notice that usually a single request is some kind of a use case. However, usually the code is so small in this area that I'm not sure it deserves a class. I can see the advantage of seeing all the interaction in one place so I just need to get used to this kind of thinking.

Terminology

In order to fit DCI into the Rails terminology I assume that we can call our thin model classes the Data layer. Our controllers actions can be wrapped into a class and called Contexts. Those actions take the Data, inject the roles (as Ruby modules) and run some Interactions.

Problems


I have already tried this approach in one small application. It worked pretty well. One of the problems that appeared was a situation when I actually want the object playing almost all of the available roles. This kind of situation often takes place at main pages or in some kind of dashboards.
I solved it by using the Cells framework. In short, Cells let you create mini-controllers (cells) that can be responsible for one part of the view. It can access the controller and execute a "mini-action". In my case I used in a way that every cell extends the website with the role that is associated with this view. It worked pretty well and I'm quite happy with the result.

Summary


In my opinion DCI fits very well with the way we create Rails apps. It simplifies the Model layer a lot. If you're happy with your current OOP design then you will probably not gain too much from DCI. If not, wikipedia entry is a good start for getting familiar with this concept at a more theoretical level. The whole concept is actually very simple and intuitive. It brings the user perspective to your code, which in my opinion is a good thing. We're here to model the real world/business etc.



 If you read this far you should Follow andrzejkrzywda on Twitter and subscribe to my RSS.

19 comments:

Michał said...

Really valuable post. DCI approach seems to be surprisingly easy to understand and to use in our projects. DCI seems to perfectly fit into MVC model.

2 questions come to my mind:

1. How do you organize your modules on disk? Do you keep them in app/models or some other directory?

2. Do you find it possible and reasonable to reuse modules/roles between between distinct objects? For instance having GroupMember role I would like to use it for both User and Conact.

Unknown said...

Any performance problems ? (You call extend on every request) or is it negligible ?

Andrzej Krzywda said...

@Michal

1.

At the moment I keep them in app/models, however I think it's best to group them in feature-specific directories, like app/models/groups and this is the direction I will go with it.

2.

Reusability of roles is an awesome idea that I want to research more. I think it's not only possible in two objects in one app, but also in other apps.

If you keep the role feature-specific and independent of other parts of your app, then it should be easy to reuse it in other apps that have similar features.

Andrzej Krzywda said...

@Karol

According to Michal's benchmarks it's negligible. .extend is not an expensive operation.

The fact that objects are thiner now can only make them perform faster.

Unknown said...

@Andrzej, @Karol

I've benchmarked #extend indeed. We have to take various aspects into account. No doubt already defined method call is way faster than extend + method call. Approximately 800-1200%.

But we can't benchmark that way, that's true only for very thin classes. We have to look for overall application performance. Usually classes are fat and initialization process is more complex for 'monolithic' objects.

Giving objects only needed capabilities significantly decreases initialization process thus we have some performance benefits.

GC is even more important. Simpler objects = less memory consumption = less garbage collector. Average rails application wastes 20%-30% time in GC.

In my opinion one can't straight said that using .extend is faster or not. It will be slower when application uses very simple objects and may be faster for very complex ones.

Basing on my observations overall performance is unaffected and as Andrzej mentioned it may even perform better.

Nerian said...

This looks amazing and definitely something I want to use.

This could be a really handy Gem.

dci generate role buyer

Unknown said...

I question where Michal got his benchmarks on using an object's singleton class versus a method defined by the originating class? I blogged about this recently finding the difference is negligible for most applications.

Unknown said...

@Wes

As I mentioned earlier to provide accurate benchmarks we would have to run them against more complex system.

I don't undermine your benchmark. However in my opinion you tested average case. You can't really state how #extend affects method call performance. This is typical benchmark trap.

For instance you don't measure GC at all which I suppose should have (positive) impact on performance in context of DCI.

I've benchmarked the worst case here. It is more DCI oriented so BlankUser singleton is slightly thiner than User object.
Here you can notice 800%-1200% #extend overhead. This is edge-case but in my opinion you should also note that in your blog post.

I don't get why you question my results. I wrote that Basing on my observations overall performance is unaffected. I didn't provide benchmarks here because we would have to take some bigger system into account to get accurate results and show some numbers.

Anyway that's cool you've provided some numbers. Thanks to that we have a bit wider look at how #extend performs

Alexander Shvets said...

it's not defined how should you discard ole from object (there is no .unextend method in ruby)...

that reminds me mixolgy gem - it could mix/unmix mixins(modules)

Andrzej Krzywda said...

@Alexander

Thanks for pointing the mixology gem.

It's an interesting point whether we should unextend a role after the "use case". I didn't think about that, but maybe that's a good idea, especially when the role are is injected from a cell.

Thanks!

Unknown said...

Hi Andrzej, thanks for that great post! In a gem I'm currently designing I use that DCI approach (I didn't know it's called like this ;-) to mix a Representer role into a model that needs to be represented (talking about REST representations).


user.extend Xml::Representer
user.to_xml


This works great. However, I wonder how to do that on a class layer, as my models need factory class methods.


user = User.from_xml(..)


Staying DCI-ish, my class User shouldn't know the .from_xml per default. Nevertheless, if I'd mix that behaviour into User, I pollute the User class since the new methods will stay over the whole process.

I'm thinking about anonymous subclasses for that


Class.new(User).extend Xml::Representer


but I can already see problems with this. Do you have any ideas about this?

Thanks again,
Nick

Andrzej Krzywda said...

@nick

If I understand you correctly you would like your module to include also class methods, right?

I use the module.extended method which is called whenever a module is extended in an object:

module EventUser
def self.extended(object)
object.class.send(:has_many, :attendances)
end

(..)
end

Unknown said...

@Andrzej: Yeah but if you do the extension like that you push new methods to the User class which won't be removed after the actual user instance gets GCed - the new methods will persist in the class. For instance, in Rails production mode, the first time you extend the user instance (and you also push methods to its class) these class methods will be around in the following requests as well. Can you see the problem now?

Andrzej Krzywda said...

@nick

Yes, I see the problem now, thanks for the explanation.

Unfortunately, I don't know any solution to this problem.

I wonder if in practice that makes a difference (apart from memory usage). Whenever a new request comes you inject a new role which should overwrite all the important methods, the ones that you want to use.

Pavel Mitin said...

Thank you for the an interesting post. I have a question about the topic.

All DCI examples, I have seen, for sake of simplicity work with couple of objects. But in the real world a programmer deals with graphs of objects. So it rather two object graphs interact in a context.

The questions are:
* should roles be propagated among graph members
* how to propogate role/roles along graph nodes

In terms of Domain-driven design the question could be: how to inject roles to agregate members?

Thanks in advance.

Jonas Bruun said...

This was quite valuable to me. Thanks Andrzej!

Anonymous said...

The lack of unextend in ruby is a serious barrier to implementing this model. (I've not examined mixology to understand its true capabilities.) Furthermore, it's not clear to me that ruby's extend is a good model for dci. In multiple places, the article talks about objects referring to each other by role, and you don't get that effect with extend.

It seems to me that they entire role-object binding has to take place via some sort of proxy object. The proxy wraps the object, and the roles get added to the proxy. When the interaction completes, the proxy is discarded and the model's methods are undisturbed.

As I said, however, this does not address how reference-by-role is implemented.

Nathan Zook

Gimi said...

@nick

I don't think you have to do `User.from_xml`. You can do it in another way, like:

Xml::Representer.parse_user(...)

Which returns a User instance.

So, I think modules which represent the logic layer could also have their module functions, not just instance methods.

Emmanuel said...

I'm just starting to scratch the surface of understanding the implications of this approach. So far, in my thought experiments, it seems like DCI could be very powerful for structuring code.

I'm not entirely sure that Object#extend is the best way to express this in Ruby, I'd prefer to see 'proper' composition (eg., `@waiter = Waiter.new(person)`), but I'm speculating a bit there. I will have to try out some experiments.

One thing that stands out is that Rails' controllers are very anemic compared to the concept of Context in DCI. Specifically, I think that Rails' convention of tying Controllers tightly to Data (Models) must be broken to allow for Contexts that express richer concepts than the simple CRUD operations that Rails' controllers are so good at. I suspect that pursuing DCI will lead to a much richer, dare I say more RESTful, approach to application design.

It looks like the Context should explicitly name the Roles involved in a given Interaction (request or series of requests), and maybe even the available outcomes (state transitions).

I may be making up that last part, but I've been starting to think about annotating available state transitions to facilitate discoverability of REST-based HTTP services, and there may be a way to do that using the DCI model.

All together DCI sounds like a really big win!