Data Context Interaction: The Evolution of the Object Oriented Paradigm
Key Takeaways
- Traditional object-oriented programming (OOP) is efficient at capturing state and expressing operations associated with an object’s state, but it struggles to express collaborations between objects. This can lead to a mismatch between source code and runtime, making programs difficult to understand.
- The Data, Context, Interaction (DCI) paradigm, invented by Trygve Reenskaug, addresses these issues by separating the stable part of a system from the use case, and introducing a new abstraction for describing interactions: the context. This allows for clear modeling of interactions between objects.
- DCI improves code readability and maintainability by separating the concerns of what the system is (data) and how it behaves (context and interaction). This separation of concerns makes it easier to understand and modify the system’s behavior without affecting its underlying data structures.
- DCI brings explicit roles into programming, arguing that objects themselves don’t have responsibilities, but roles do. This allows for more flexibility and adaptability in software design, as objects can change roles depending on the context.
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. RubyString
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 2

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.
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.
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:
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. [gist id=”3333664″] 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: [gist id=”3333670″] 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 thetransfer
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:
[gist id=”3333671″]
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:
[gist id=”3333675″]
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:- Clean Ruby by Jim Gay. This book is filling the need for a practical introduction to DCI for Rubyists. It’s still work in progress, but it looks very promising.
- Lean Architecture: for Agile Software Development by James O. Coplien and Gertrud Bjørnvig. “This is not only the market’s first book on Lean Architecture and Agile development, but it clarifies the difference between these two powerful approaches and shows how they can be combined. It is also the first book to present Trygve Reenskaug’s new software architecture called DCI: Data, Context, and Interaction. DCI is to the programmer as the classic MVC architecture is to the end user: a software approach that puts people first.”
- Object Design: Roles, Responsibilities, and Collaborations by Rebecca Wirfs-Brock and Alan McKean. As this book was published in 2002, it doesn’t cover DCI. But it’s a great material on object design, using roles, and modelling collaborations. These topics are closely related to the core ideas behind DCI.
Frequently Asked Questions (FAQs) about DCI (Data, Context, Interaction)
What is the main difference between DCI and traditional Object-Oriented Programming (OOP)?
Traditional OOP focuses on the classification of objects based on their properties and behaviors, which are defined in classes. However, DCI extends this concept by introducing the idea of roles that objects can play in different contexts. In DCI, objects are not just defined by their classes, but also by the roles they can play in different interactions. This allows for more flexibility and adaptability in software design, as objects can change roles depending on the context.
How does DCI improve code readability and maintainability?
DCI improves code readability and maintainability by separating the concerns of what the system is (data) from how it behaves (context and interaction). This separation of concerns makes it easier to understand and modify the system’s behavior without affecting its underlying data structures. Moreover, by focusing on the interactions between objects rather than their individual behaviors, DCI promotes a more holistic understanding of the system’s behavior.
Can DCI be used with any programming language?
While DCI was initially developed for use with object-oriented programming languages, it can be adapted to other programming paradigms as well. The key is to understand the principles of DCI – data, context, and interaction – and apply them in a way that makes sense for the particular language and paradigm you are using.
What are the challenges in implementing DCI?
One of the main challenges in implementing DCI is that it requires a shift in mindset from traditional object-oriented programming. Developers need to think in terms of roles and interactions, rather than just classes and objects. Additionally, not all programming languages support the concept of roles natively, which can make the implementation of DCI more complex.
How does DCI handle data persistence?
In DCI, data persistence is typically handled by the data objects themselves. These objects are responsible for storing their own state and retrieving it when necessary. This approach allows for a clear separation between the system’s behavior (defined by the context and interactions) and its state (defined by the data objects).
Can DCI be used in conjunction with other design patterns?
Yes, DCI can be used in conjunction with other design patterns. In fact, DCI can be seen as a higher-level pattern that provides a framework for organizing and structuring other design patterns. By focusing on the interactions between objects, DCI can help to clarify the roles and responsibilities of different patterns within the system.
How does DCI handle error handling and exception management?
Error handling and exception management in DCI are typically handled by the context. The context is responsible for coordinating the interactions between objects, and as such, it is well-placed to handle any errors or exceptions that may occur during these interactions.
How does DCI support agile development practices?
DCI supports agile development practices by promoting a flexible and adaptable approach to software design. By separating the concerns of data, context, and interaction, DCI allows for changes to be made to the system’s behavior without affecting its underlying data structures. This makes it easier to respond to changing requirements and to iterate on the design quickly and effectively.
How does DCI handle concurrency and multi-threading?
Concurrency and multi-threading in DCI are typically handled by the context. The context is responsible for coordinating the interactions between objects, and as such, it can manage the execution of these interactions across multiple threads.
What are some practical examples of applications that use DCI?
DCI has been used in a variety of applications, ranging from web applications to embedded systems. Some examples include the Squeak/Smalltalk programming environment, the Qi4j framework for Java, and the Marvin conversational AI platform. These applications demonstrate the flexibility and adaptability of DCI in different domains and contexts.
Victor Savkin is a developer interested in Domain Driven Design, Enterprise Architecture, and Domain Specific Languages. He works on large enterprise applications written in Rails. Being a language nerd he spends a lot of his time playing with Smalltalk, Groovy, Scala, Clojure, Ruby, and Ioke.