Over a million developers have joined DZone.

DCI in Ruby (Part 1)

DZone 's Guide to

DCI in Ruby (Part 1)

· ·
Free Resource

DCI (Data Context Interaction) is a new way to look at object-oriented programming. If you’d like to read some theory to see the difference between DCI and traditional OOP there is a nice article covering the topic:


And this presentation can be very helpful too:


It isn’t easy to use DCI in Java as by its nature DCI requires sharing behavior between classes and Java doesn’t provide any decent ways to do it. But many modern languages do including Ruby. To demonstrate how to use mixins in Ruby for implementing DCI I’ll write a simple app.


  • We have users
  • We have companies
  • Users can follow users
  • Users can follow companies
  • Users are entities stored in a database
  • Companies are entities stored in a database

Basically, we have two domain classes: users and companies and use cases: when a user starts following a company and he starts following another user.

Firstly, let’s create our domain classes:

class User
  attr_reader :id, :name, :age, :followers

  def initialize id, name, age, followers = []
    @id, @name, @age, @followers = id, name, age, followers

class Company
  attr_reader :id, :name, :country, :followers

  def initialize id, name, country, followers = []
    @id, @name, @country, @followers = id, name, country, followers

And a simple Database class representing persistent infrastructure of a real application:

class Database

  USERS = {
    1 => User.new(1, "John", 25),
    2 => User.new(2, "Sam", 26)

    1 => Company.new(1, "Big Company", "Canada")

  def find_user_by_id id

  def find_company_by_id id

  def update_user user
    USERS[user.id] = user

  def update_company company
    COMPANIES[company.id] = company

Domain objects in DCI aren’t smart. They don’t provide methods for all possible use cases. They don’t interact with each other in complex ways. Instead, they have a set of fields and a bunch of convenient methods to access them.

All our business logic is concentrated in roles. Role is a piece of behavior that we can mix into our domain classes to solve business problems. We’ll need two roles for our toy application:

module Follower

module Following
  def add_follower follower
    followers << follower

Follower is a marker role. It isn’t necessary to create such a kind of a role but I like to do it as it clarifies my intent.

The only part that left is a context, which will extract domain objects from the database, assign some roles to them and perform a business transaction:

class FollowersListContext

  def initialize db
    @db = db

  def add_follower_to_user following_user_id, follower_user_id
    following = @db.find_user_by_id following_user_id
    follower = @db.find_user_by_id follower_user_id

    following.extend Following
    follower.extend Follower

    following.add_follower follower

    @db.update_user following

  def add_follower_to_company following_company_id, follower_user_id
    following = @db.find_company_by_id following_company_id
    follower = @db.find_user_by_id follower_user_id

    following.extend Following
    follower.extend Follower

    following.add_follower follower

    @db.update_company following

#using our context
db = Database.new
context = FollowersListContext.new db
context.add_follower_to_user 1, 2

It may not be the most impressive example as we share only one line of code but it shows how all pieces work together. In a real word example roles will do much more than just adding an item to a collection. As a result this kind of decomposition will allow us to split complex behavior and avoid monster classes with thousands lines of code.

Now let’s take a look at a few ways of doing our code look a bit better.

The simplest way is to define an alias for the extend method. So instead of extending objects we’ll assign roles.

class Object
  define_method :add_role do |role|
    self.extend role

following = db.find_company_by_id 1
follower = db.find_user_by_id 2
following.add_role Following
follower.add_role Follower

It’s also not a problem to make it the other way around and make our roles responsible for modifying objects.

class Module
  def played_by obj
    obj.extend self

user_a = db.find_company_by_id 1
user_b = db.find_user_by_id 2
perform_operation(Following.played_by(user_a), Follower.played_by(user_b))

If you need more flexibility you can always add a function that will add required roles to an object.

module FollowerRole

def Follower object
  object.extend FollowerRole

follower = Follower(user_a)

Another fancy way of doing it is returning an object with a role assigned to it.

following = user_a.in_role Following
follower = user_b.in_role Follower

To make it look a bit more declarative we can specify what roles we want to assign to our domain objects in a array. The using method will iterate over the array and add all necessary roles. So our context will look like this:

using [user_a, :as, Following,
          user_b, :as, Follower]   do |following, follower|


To Sum Up

Though Ruby doesn’t have the concept of a role you can easily mimic it with mixins. There are tons of ways to do it, from the most simple ones to robust DSLs.

DCI is definitely gaining some popularity right now, so there is a chance that the next Rails app you’ll work on will use it in some form. But even if it don’t DCI will give you one more way to think about OO design.


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}