Data Context Interaction: The Evolution of the Object Oriented Paradigm

This article is a practical introduction to DCI (Data Context Interaction). I’m not going to explain all theory behind it. Instead, I’m going to show you what kind of problems DCI is trying to solve and how it can be implemented in Ruby.

What OO Does Well

Let’s start by looking at some problems where traditional object oriented programming does a good job.

Object oriented programming is very good at capturing state. Classes, fields, and properties are powerful features allowing you to define state and work with it explicitly. Since we have these means of expressing state built into programming languages, reasoning about an object’s state is straightforward both at compile time and runtime. At compile time we can look at an object’s class definition. At runtime we can ask an object about its fields.

Another problem that is solved quite well by all object oriented languages is expressing operations associated with an object’s state. Such operations don’t involve any collaborations. They are local to the owning object. We express local operations by defining methods for a class. When we define a method for a class and create an object of the class, we know that the object will have that method. It’s pretty descriptive.

Ruby String is a good example of an object that has only local operations. Every String has an array of bytes or characters, and all operations work with that array. This object is self-contained; no collaborations with other objects are required. I don’t think anyone had any problems understanding how to use a String. This is the kind of problem OO languages were built to solve.

What OO Fails to Do

What object oriented programming fails to do is express collaborations between objects. To show you exactly what I mean, let’s take a look at two system operations (two use cases) requiring the same group of objects collaborating with each other.

Use Case 1

Use Case 1

Here we have a system operation with four objects talking to each other.

Use Case 2

Use Case 2

Here we have another use case, another system operation using the same group of objects. But you can see the collaboration pattern is different. The messages are different.

System Operations Aren’t Represented In The Code

We saw two system operations implementing Use Case 1 and Use Case 2. How do we represent them in the code? Ideally, I’d like to have the ability to open one file and figure out the collaboration pattern of the use case I’m working on. If I’m working on Use Case 1, I don’t want to know anything about Use Case 2. That’s what I’d consider a successful representation of system operations in the code.

Unfortunately, the traditional object oriented programming doesn’t give us any means of doing that. It gives us some tools to express the state of objects, and attach local behavior to those objects. But we don’t have any good ways to describe how objects communicate at runtime to execute a use case. Therefore, system operations aren’t represented in the code.

Source Code != Runtime

In the end, we still write code and program system operations somehow. How do we do it? We split them into lots of small methods that we put into lots of different objects.

All Methods

What we see here is all the methods required by all the use cases are jammed into these objects. The methods required to perform the first use case are green, the second use case are red. In addition, these objects have some local methods. These local methods are used by the green and red methods.

The problem this picture illustrates is that the source code doesn’t reflect what happens at runtime. The source code tells us about four separate objects with a whole lot of methods in each of them. The runtime tells us that we have the four objects talking to each other and only a small subset of those methods is relevant to a particular use case. This mismatch makes programs hard to understand. The source code tells us one story, the runtime tells us a completely different story.

On top of that, there is no way to define system operations (use cases) explicitly, so we have to trace all method calls to get an idea of what is going on. There is no file we can open to figure out a particular use case. Even worse, since all classes contain methods for lots of different use cases, we have to spend a lot of time filtering them out.

DCI to the Rescue

DCI is a paradigm invented by Trygve Reenskaug (the inventor of the MVC pattern) to solve these problems.

Use Case 1 (DCI)

Let’s take at a look at the first use case implemented in the DCI style.

Use Case 1 (DCI)

What we have here is the separation of the stable part of the system, containing only data and local methods, from the use case. All traditional object oriented techniques can be used to model the stable part. In particular, I’d recommend using domain driven design techniques such as aggregates and repositories. But there is no contextual behavior there, and no interactions – only local methods.

How Do We Model Interactions?

We have a new abstraction for describing interactions: the context. It’s a class including all roles for a given use case. Every role is a collaborator in the interaction and it is played by an object. As you can see, the contextual behavior is concentrated in the roles. The context just assigns the roles to the objects and after that triggers the interaction.

Use Case 2 (DCI)

The second use case implemented in the DCI style:

Use Case 2 (DCI)

I’d like to point out that our objects (Object A-D) stay the same. We didn’t have to add any methods to support the second use case. All the methods we have there are fundamental, self-contained, and local. All use cases specific behavior was extracted into the contexts and roles.

Another thing is, we don’t see the red and green methods at the same time. Every context contains only methods required to execute itself.

It may sound too abstract, so let’s take a look at a code example to see how it can be implemented in Ruby.

Code Example

This is a hello world example for DCI. Everyone interested in DCI starts by transferring money from one account to another.

I realize the example I’m about to show is oversimplified. And since it’s so simple, it can be successfully implemented using services, regular entities, or functions. So look at this example as an illustration of how you would structure your code.

As we’re talking about transferring money from one account to another, we’ll need to store information about accounts somehow. The Account class is responsible for doing this. It stores an account’s balance and list of transactions.

As you can see, all the methods here are local and context independent. Account knows nothing about transferring money. It’s only responsible for increasing and decreasing its balance. The logic of transferring money is in the context:

I’m fetching two accounts from the database, then I’m instantiating the context, and then calling ‘transfer’. You may have noticed I’m passing the two accounts to the constructor and the amount to the transfer method. By doing that I’m trying to communicate which objects are actors in this interaction and which are just data. The accounts are actors, they have behavior. The amount is data.

Next, I’m assigning the roles to the account objects in my constructor. I’m teaching these data objects how to be a source account and a destination account.

Finally, I’m triggering this interaction by calling “transfer_out” on the source account. In this example, the context just triggers an interaction, but in some complicated cases it can also coordinate actors.

Now let’s take a look at how the roles are implemented:

First, I’m checking that the source account has enough money. Then, I’m decreasing the balance. After that, I’m getting the destination account through the context variable to tell it to receive the money.

There are a few interesting things here:
* The separation between the stable behavior and contextual behavior. Account is a dump class that only knows how to manipulate data. All the checks, all the business logic is in the context.
* The roles access other collaborators through the context variable. Once again, it’s done to separate actors from data. If I pass everything as an argument, how will I know what is an actor and what is not? Therefore, all the actors are accessed through the context, and all data objects are passed as arguments.

The context and both roles:

What We Got

Locality

The problem with the traditional object orientation is that the algorithm gets scattered across many different files. It’s solved by DCI. When you want to know how a particular use case is implemented you need to open only one file.

Focus

A context contains only the methods that are part of the use case it represents. So you don’t have to scan through dozens (or even hundreds) of methods that have nothing with the problem you are working on.

“What the system is” and “What the system does”

“What the system is” is all data objects and their local methods. Usually, this part of the system is super stable. “What the system does” is contextual behavior that changes rapidly. Separating stable parts from rapidly changing ones is vital for building stable software. And DCI provides this separation:
* A DCI Class says everything about the inside of an object and nothing about its neighbors (“What the system is”).
* A DCI Context says everything about a network of communicating objects and nothing about their insides (“What the system does”).

Source Code == Runtime

Another thing is, the source code matches the runtime. The runtime tells us that there are two accounts and an amount. That’s what you see when you open the context.

Roles are explicit

The greatest thing DCI brings is explicit roles. A lot of designers agree that objects by themselves don’t have responsibilities – roles do. For instance, take me as an example of an object. I have the following properties: I’s born in Russian; my name is Victor; my weight is about 65kg. Do these properties really imply some high level responsibilities? They don’t. But when I come home and start playing the role of a husband, I become responsible for all that husband’s stuff. So objects play roles. The fact that roles aren’t first class citizens in the traditional object orientation is just wrong.

Resources

If you think this idea is interesting, you should check out the following resources:

If you prefer reading books, these are three books I can recommend:

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • rebo

    I have to say this is the first explanation of DCI that I have fully understood. Thank you so much for posting this. It is all much clearer now.

  • http://mathaba.net Baggi

    This is just another pattern that’s all.

  • Frank Wang

    Thank you for this excellent article. I do have a question though. In transfer_out method, does it make sense to remove the method call to destination account and put it in transfer method of TransferringMoney?

    def transfer_out amount
    raise “Insufficient funds” if balance < amount
    decrease_balance amount
    update_log "Transferred out", amount
    end

    def transfer amount
    in_context do
    source_account.transfer_out amount
    destination_account.transfer_in amount
    end
    end

    I'm from C# background. Hope this is not a silly question.

    • http://victorsavkin.com Victor Savkin

      Hi Frank,

      This is not a silly question. It really depends on how you think about the problem.

      To make this example more concrete, imagine instead of accounts we have Alice and Bob. And Alice wants to give Bob fifty dollars.

      * She can hand $50 to Bob. That’s the way it’s implemented right now.
      * She can use some sort of middleman who will take the money from her and give it to Bob.

      Both approaches are valid.

      Keep in mind though, if you move too much logic into the context itself, you’ll get the centralized control style. Your context will become the center of making all decisions. The way it’s done right now is closer to the delegated control style. For this particular example, it doesn’t really matter, but I find the delegated control style works much better for complex interactions.

      Cheers,
      Victor

  • Mike West

    Shouldn’t the transfer method deal with transferring the money in

    def transfer amount
    in_context do
    source_account.transfer_out amount
    destination_account.transfer_in amount
    end
    end

    Instead of the source account role need to know about the destination account? Seen as that seems to be the responsibility of “TransferringMoney”

    • http://victorsavkin.com Victor Savkin

      Hi Mike,

      Thanks for your comment. Please, see me response to Frank Wang’s question.

      Cheers,
      Victor

  • http://www.agilefreaks.com Calin

    Hi Viktor,

    Great article, thank you very much.

    Just one question, can I see the implementation of the Context module, I don’t see the purpose of the in_context (except for it’s semantic purpose)

    in_context do
    source_account.transfer_out amount
    end

    Thanks,

    • http://victorsavkin.com Victor Savkin

      Hi Calin,

      Thanks for your comment.

      module ContextAccessor
      def context
      Thread.current[:context]
      end
      end

      module Context
      include ContextAccessor

      def context=(ctx)
      Thread.current[:context] = ctx
      end

      def in_context
      old_context = self.context
      self.context = self
      res = yield
      self.context = old_context
      res
      end

      end

      As you can see, in_context sets the context variable (it’s a thread local global variable) that all role players can access.

      In DCI, you can use one context inside another one. To support it we need to maintain a stack of instantiated contexts. That’s what ‘old_context = self.context’ does.

      Cheers,
      Victor

      • http://www.agilefreaks.com Calin

        Thanks Victor, this makes sens

      • Hernan Schmidt

        Hi Viktor, thanks for this article! You said:
        “In DCI, you can use one context inside another one. To support it we need to maintain a stack of instantiated contexts.”
        I still don’t understand why we need to maintain a stack of instantiated contexts in order to use on inside another… Could you elaborate, please?
        Thanks!

        • http://victorsavkin.com Victor Savkin

          Sure, imagine you have two contexts ContextA and ContextB::

          class ContextA
          ….
          def execute
          in_context do #Here we set Thread.current[:context] to this instance of ContextA
          # at this point, Thread.current[:context] is ContextA

          B.new.execute # Here we set Thread.current[:context] to this instance of ContextB

          # if we don’t maintain the stack of contexts, Thread.current[:context] will be ContextB. That’s wrong.
          # if we do maintain the stack of contexts, we can restore the previous context from the stack (ContextA).
          end
          end
          end

          #
          class ContextB

          def execute
          in_context do #Here we set Thread.current[:context] to this instance of ContextB

          end
          end
          end

          Thread.current[:context] is a global variable that all role players use to access the current context.

          First, we instantiate and run ContextA. This new instance of ContextA becomes the current context. Thus, the global variable must point to it. So all the roles of ContextA will be able to access it.

          Then, we instantiate ContextB inside ContextA, and this instance of ContextB becomes the current context. Therefore, we must change the global variable to point to it.

          Finally, ContextB returns, and the first instance of ContextA becomes the current context again. Therefore, we must change the global variable to point to it.

          If it’s still confusing, I recommend you take a look at this paper:
          http://folk.uio.no/trygver/2012/DCIExecutionModel-2.1.pdf

          Cheers,
          Victor

          • Hernan Schmidt

            Great, thanks for the explanation and the link to the paper. I’ll give it a read. I was confused because I didn’t notice that Roles had to have access to the current Context. Thanks!

  • Tom Cloyd

    I’m a rank amateur Ruby programmer, and I love the DESIGN of Ruby – the form of it. This article intrigued me initially because I had not the slightest idea what it was about. Then it very quickly became obvious to me, and I realized that (to me at least) this whole DCI concept is strongly related to behavior driven development. What do you think?

    As a way to get solid work working for people quickly, this all has a huge appeal for me.

    Thanks so much for this addition to my learning. It’s very helpful. I WILL be digging into this further – because of your article.

    • http://victorsavkin.com Victor Savkin

      Thanks Tom for your comment. I’m glad that the article sparkled your interest in DCI.

      Cheers,
      Victor

  • http://www.alterweb.net mezzanine

    Great! Thanks.

  • Pyro

    thanks!

  • http://www.agilefreaks.com Calin

    Hi Victor,

    I’ve took a TDD approach in implementing your example, you can find the result here https://github.com/balauru/dci-example, is this close to what you will write in an actual application?

    Regards,

    • http://victorsavkin.com Victor Savkin

      Hi Calin,

      Thanks for your comment. Yup, it’s close. Of course, a real world context is usually more complicated than that, but the structure is similar.

      Cheers,
      Victor

  • http://tripledogdare.net Evan Light

    As a developer who has worked in the field for 17 years, 13 of them working with OO, I’m unconvinced that DCI is a step forward.

    That’s quite the Object-Oriented straw man that you’ve beaten up.

    There’s nothing about OO that prevents it from expressing “collaboration between objects”. You are confusing the Ruby community’s perseveration on the Single Responsibility Principle with OO.

    SRP (Single Responsibility Principle) attempts to reduce the number of reasons that an object would change. The more fan out dependencies on an object (that is, the more objects that the current object directly messages) the more reasons that it has to change.

    Reducing dependencies between objects, also known as “loose coupling”, simplifies testing and facilitates changing the code.

    However, due to lack of a canonical definition of OO, adding dependencies for the sake of defining a process can still be considered OO. For that matter, DCI smacks of the Mediator Pattern (GoF: http://en.wikipedia.org/wiki/Mediator_pattern).

    If you want to read more, see http://evan.tiggerpalace.com/articles/2012/11/21/use-rails-until-it-hurts/