NAME

INTRODUCTION TO DCI

The DCI concept was created by Trygve Reenskaug, (inventor of MVC) and James Coplien.

DCI Stands for Data, Context, Interactions. It was created to solve the problem of unpredictable emergent behavior in networks of interacting objects. This problem shows itself in complex OOP projects, most commonly in projects with deep polymorphism. This is a problem that Procedural/Imperative Programming does not have.

DCI does not replace OOP, instead it augments it with lessons learned from looking back at Procedural Programming. It defines a way to encapsulate use cases into a single place. This provides an advantage to the programmer by reducing the number of interactions that need to be tracked. Another advantage is the reduction of side-effects between contexts.

Another way to look at it is that a DCI implementation is much more maintainable as a project matures. Changes to requirements and additional features cause clean OOP project to degrade into spaghetti. DCI on the other hand maintains code clarity under changing requirements.

TERMINOLOGY

Data

Refers to "what the system is." These objects represent well defined objects that should rarely change. They should encapsulate only basic CRUD methods. A common example would be a BankAccount object.

Context

A context implements one or more use cases. A use case can be a well defined workflow in business logic, or an algorithm. A common example would be a transfer between bank accounts. The context defines what objects are required, such as a DestinationAccount, an OriginAccount, and a transfer amount. The context also provides a point of entry to kick off the task and see it to completion.

Interactions

Refers to "what the system does." Typically implemented by defining roles that take part in a context. Examples roles within an FundTransfer context would be DestinationAccount and OriginAccount. Roles delegate CRUD operations to the data objects. A role would only encapsulate methods applicable to that role.

To elaborate, a DestinationAccount would implement a deposit() method, but has no need of a withdrawal() method. An OriginAccount would implement a withdrawal() method, but has no need of a deposit() method.

COMPLETE EXAMPLE

Here we will implement the same thing in both DCI and OOP. We will start off with a set of requirements and code that implements them in each architecture. This example mimics a real world situation in which a new feature is added years after the original requirements were implemented.

You may notice that the DCI version is longer than the OOP version. DCI does not claim to reduce code in the offset. DCI is in fact longer at the beginning, but does offer code saving in the form of avoiding costly refactors. DCI is intended to be more future-proof, allowing new features and requirements to be added with less overhead.

At the end of the example we will present a few new-feature exercises, thinking through these exercises will bring home the benefit of the DCI system.

PHASE 1: INITIAL REQUIREMENTS

We will be implementing a number system that has a base number type, an integer type, and a float type. We will also implement addition of two numbers. Note that most of the code is common between the OOP and DCI versions.

You can see these implementations in action in the t/dci_intro_oop.t and t/dci_intro_dci.t tests. The common code is found in t/lib/ which is used by both tests.

COMMON CODE

This code is common to both the DCI and OOP versions. Each section below will simply list additional code for each object, and additional objects.

PHASE 2: A NEW FEATURE

We will be adding a new number type, a fraction. This feature request comes years later and there are thousands of lines of code that use the existing Integer and Float classes, refactoring is not an option. We need to work in the new Fraction class without braking anything old.

OOP CHANGES

There are some important considerations:

Adding fractions and other numbers is tricky.

Fractions are essentially a division that has not happened yet. In many cases a fraction, or rational number, cannot be represented completely in decimal form. For instance 1/3 = 0.3333-> on to infinity. When dealing with fractions it is important to do all the arithmetic before converting the fraction to decimal form.

Faction addition belongs in the fraction class.

This is encapsulation, the base class should not implement logic specific to a single subclass.

How do we add a fraction to a non-fraction?

We either need to convert the fraction to a float before adding, or we need to convert the left operand into the more precise fraction type, and then add. The first could potentially lose precision, and is not the ideal option.

We also need to modify the base class so that fractions work when added to other numbers. We will do this in a way that does not care if the subclass is a fraction, or some other type of complex number.

Note: This is the 'correct' way to do this. Though in most projects, specially with time constraints, this would likely just be a conditional looking for the fraction type. Conditioning on fraction type is not ideal should we need to add new complex types later. The DCI version will not have this problem.

DCI CHANGES

The DCI changes may seem quite long, however it should be noted that a lot of this has to do with the overhead of writing new modules as opposed to adding code to existing ones. Another reason for this is because DCI practically forces us to write this in a way that leaves the code maintainable. If we want to add new complex types after this it will be trivial.

First we need to update the context, it is presented here in its entirety so that you do not need to look back at the old version.

Now we need to add a cast class for numbers used in Math contexts. In DCI this would normally be called a 'role' but we use the term 'cast' to avoid conflict with Moose and many other projects which use the term 'role'.

THOUGHT EXERCISES

These are exercises for you to think about. If you think about how to solve these problems using both the OOP version and the DCI version you will see where DCI benefits. These added features or requirements could cause an OOP project to quickly degrade. DCI on the other hand already solved most of them when the fraction type was added.

OOP

How hard would it be to add another complex type? It may initially seem easy, but consider the add() method. How would it handle 2 complex types used in an addition? Currently it would use the add method from the operand on the left after converting the operand on the right. What if the left operand is a less precise type?

What if you also had a multiply method, or other complex operations?

Does your system depend on the most accurate math, or will converting things to less precise types still provide an acceptable result?

How would you add a precision to your types to ensure things are always converted to the most precise type?

How would you add another complex type with precision between existing ones?

DCI

Lets say you did not separate conversion into a context of its own. Now you want to implement a subtract() use-case. This use case also needs conversion, how much code needs to change? The answer is simple, move the conversion logic into a context, and use it in both use-cases, nothing else need change.

How hard is it to add a new complex type? What needs to change? The answer is that you need to add the class to the Example::Math::Number Cast variables for precision and complex types. You also need to implement logic in the Conversion context to convert between fraction and your new type. Lastly you need to implement logic int he Add context which adds two of your new type together.

When adding the fraction type you may not have had the foresight to implement the precision sorting. Instead you simply check if it is a primitive float/int, or a fraction. How hard would it be to refactor it to use the precision system we have now?

Look back at the thought exercises for the OOP version, how difficult or easy are they in DCI? How many of them are even an issue in DCI?

AUTHORS

COPYRIGHT

Copyright (C) 2011 Chad Granum

DCI is free software; Standard perl licence.

DCI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.

ACHNOWLEDGEMENTS

The DCI concept was created by Trygve Reenskaug, (inventor of MVC) and James Coplien.

Module Install Instructions

To install DCI::Introduction, simply copy and paste either of the commands in to your terminal