Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to understand DCI pattern

According to Wikipedia Data, context and interaction (DCI) is a paradigm used in computer software to program systems of communicating objects. Here I am not clear about the problem which DCI tries to solve. Can you explain it with simple example? What is Data, Context and Interactions in your example?

like image 677
Malintha Avatar asked Jul 24 '17 10:07

Malintha


People also ask

What is DCI model?

Data, context, and interaction (DCI) is a paradigm used in computer software to program systems of communicating objects.

What is context in Oop?

Context is a span surrounding statements and expressions. Context defines the set of actions you can take, i.e. methods to invoke, variables to use, etc. The following, among others, create a context: a class, a method (or a function, a procedure, etc.), a block of code.


2 Answers

An easy way for me to understand it is with the classic banking application example. In this example, I'll use Rails.

Let's say there's a feature of our app where users can transfer money from one account to another account.

We might have a controller that looks like this:

# app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
  def transfer
    @source_account = Account.find(params[:id])
    @destination_account = Account.find(params[:destination_id])
    @amount = params[:amount]

    if @source_account.transfer_to(@destination_account, @amount)
      flash[:success] = "Successfully transferred #{@amount} to #{@destination_account}"
      redirect_to @source_account
    else
      flash[:error] = "There was a problem transferring money to #{@destination_account}"
      render :transfer
    end
  end
end

In here, we're calling transfer_to on one of our Account objects. This method is defined in the Account model.

# app/models/account.rb
class Account < ActiveRecord::Base
  def transfer_to(destination_account, amount)
    destination_account.balance += amount
    self.balance -= amount
    save
  end
end

This is a traditional MVC solution - when the transfer method on the controller is called, we instantiate a couple of model objects and call the behavior defined on the model. Like Robert said, the business logic is split up, and we have to look in a couple different places to understand the code.

The downside of this approach is that we can end up with a lot of behavior defined inside of our models that they don't always need and lacks context. If you've worked on a large project before, it's not long before model files grow to be several hundred or even a couple thousand lines of code because all of the behavior is defined inside of them.

DCI can help solve this problem by giving our data models behavior only within certain contexts that they need to use that behavior. Let's apply this to our bank application example.

In our example, the Context is transferring money. The Data would be the Account objects. The Behavior is the ability to transfer money. The Interaction is the actual action of transferring money from one account to the other. It could look something like this:

# app/contexts/transferring_money.rb
class TransferringMoney # this is our Context
  def initialize(source_account, destination_account) # these objects are our Data
    @source_account = source_account
    @destination_account = destination_account

    assign_roles(source_account)
  end

  def transfer(amount) # here is the Interaction
    @source_account.transfer_to(@destination_account, amount)
  end

  private

  def assign_roles(source_account)
    source_account.extend Transferrer
  end

  module Transferrer
    def transfer_to(destination_account, amount)
      destination_account.balance += amount
      self.balance -= amount
      save
    end
  end
end

As you can see from the example, the Data is given its behavior within the Context at runtime, when we call source_account.extend Transferrer. The transfer method is where the Interaction takes place. This prevents us from splitting logic into separate files and it's all contained inside one Context class.

We would call it from the controller like this:

# app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
  def transfer
    @source_account = Account.find(params[:id])
    @destination_account = Account.find(params[:destination_id])
    @amount = params[:amount]

    if TransferringMoney.new(@source_account, @destination_account).transfer(@amount)
      flash[:success] = "Successfully transferred #{@amount} to #{@destination_account}"
      redirect_to @source_account
    else
      flash[:error] = "There was a problem transferring money to #{@destination_account}"
      render :transfer
    end
  end
end

With such a simple example, this might seem like more trouble than it's worth, but when apps grow really large and we add more and more behavior to our models, DCI becomes more useful because we only add behavior to our models within the context of some specific interaction. This way, model behavior is contextual and our controllers and models are a lot smaller.

like image 71
Jared Rader Avatar answered Oct 05 '22 04:10

Jared Rader


The key aspects of the DCI architecture are:

  • Separating what the system is (data) from what it does (function). Data and function have different rates of change so they should be separated, not as it currently is, put in classes together.
  • Create a direct mapping from the user's mental model to code. The computer should think as the user, not the other way around, and the code should reflect that.
  • Make system behavior a first class entity.
  • Great code readability with no surprises at runtime.

I highlighted user's mental model because that's what it's really about. The system architecture should be based on the users thought processes, not the engineers.

Of course the mental model should be discussed and produced by everyone related to the project, but that's rare. Usually the engineers will code according to patterns, decomposition, inheritance, polymorphism, and the part of the code that makes sense to the user is obfuscated behind layers of structure.

This is what DCI tries to remedy. It has run across some resistance over the years, in my opinion because engineers love their structure and classes, so they focus primarily on that.

An illustrating code example would be too lengthy to post here, but the mindset is more important anyway. It's about objects dynamically working together, to solve specific problems. I have made a larger tutorial here, with some code: https://github.com/ciscoheat/haxedci-example

Also, I highly recommend the video A glimpse of Trygve for further explanation, by one of the DCI authors, James Coplien.

like image 32
ciscoheat Avatar answered Oct 05 '22 03:10

ciscoheat