Pages

Monday, May 4, 2009

DCI in C#

The DCI, or the Data Context and Interaction is an approach to implementing domain logic and use cases in terms of the roles domain objects play at runtime. It is being developed and promoted by Trygve Reenskaug and James O. Coplien, who have written a very good introductory article about DCI at Artima.com. Here I'll stick to a very brief explanation and then jump into implementation of DCI in C#.

DCI at a Glance

I find that the DCI design is most easily understood in the context of Model View Controller. MVC, in its purest form, allows the end user to reach through the view, via the controller into the model and manipulate it directly. This is fine as longs the user is actually able to perform his tasks simply by massaging the domain objects. As soon as the application needs to support some sort of coordinated sequence of actions that the user is not allowed to control on his own, something more is needed. Where is that control coded? Is it in the controller? Is it spread out between a number of domain classes? Is it in a business or service layer on top on the domain model? -DCI offers a different take:

DCI tells us to think in terms of three main concepts: The data captured in the domain model, the interactions between domain objects happening at runtime and the context in which the interactions happen. DCI also tells us to implement interaction in terms of roles: Domain objects take on different roles throughout the execution of an application. The interactions between domain objects happen in terms of these roles. The context of an interaction is the collection of runtime objects that will take part in the interaction, each referenced by role. The nice thing is that those coordinated sequences of actions are implemented directly in the role that will plays the lead part in the interaction. The context will provide the role with all the collaborators it needs.

Right, enough explanation, the article mentioned above does a better job at explaning DCI anyway, so I'll just jump right into the C# code.

The C# Code

I'll walk through how the data, the context, and the role can be implemented in C#. To do this I'll use a simplistic banking example and implement a simplified money transfer use case in the role TranferMoneySource. The example is the same as the one used in the article linked above.

The domain model (i..e the D in DCI) consists of a savings account and an abstract account:

public abstract class Account

{

public double Balance { get; protected set; }

public void Withdraw(double amount)

{

Balance -= amount;

}

public void Deposit(double amount)

{

Balance += amount;

}

public void Log(string message)

{

Console.WriteLine(message);

}

}

public class SavingsAccount

: Account, TransferMoneySource, TransferMoneySink

{

public SavingsAccount()

{

Balance = 1000;

}

public override string ToString()

{

return "Balance " + Balance;

}

}

The TransferMoneySource andTransferMoneySink are roles, and I'll get back to those. For now just notice that the roles must be interfaces, since the single inheritance of C# is used to extend the abstract Account.

The context (the C) in which the money tranfer use case is executed is the TransferMoneyContext:

public class TransferMoneyContext

{

public TransferMoneySource Source { get; private set; }

public TransferMoneySink Sink { get; private set; }

public double Amount { get; private set; }

public TransferMoneyContext(TransferMoneySource source,

TransferMoneySink sink, double amount)

{

Source = source;

Sink = sink;

Amount = amount;

}

public void Execute()

{

Source.TransferTo(Sink, Amount);

}

}

The context captures the objects necesarry to execute the money transfer use case in terms of the roles they play in that use case. I.e. the context captures the account that acts as source and the account that acts as sink. The context just holds on to these object until the Execute method is called. At that point the control is transferred to the role capable of running the use case - the source account.

Lastly the role implementation remains. The use case (the I) is captured in the role, and the role is attached to any number of domain objects. As mentioned above the roles are interfaces, but they also need to implement the use case. This is achieved through extension methods attached to the role interfaces. This enables us to tie behaviour to interfaces, which is what we need in order to tie the use case implementation to the role, which in turn can be tied to any domain object. The roles are implemented like this:

public interface TransferMoneySink

{

void Deposit(double amount);

void Log(string message);

}

public interface TransferMoneySource

{

double Balance { get; }

void Withdraw(double amount);

void Log(string message);

}

public static class TransferMoneySourceTrait

{

public static void TransferTo(

this TransferMoneySource self,

TransferMoneySink recipient, double amount)

{

// The implementation of the use case

if (self.Balance <>

{

throw new ApplicationException(

"insufficient funds");

}

self.Withdraw(amount);

self.Log("Withdrawing " + amount);

recipient.Deposit(amount);

recipient.Log("Depositing " + amount);

}

}

In essence the above usage of extension methods on interfaces emulates traits in C#.

DCI on .NET

While this offers one way of doing DCI on the .NET platform it is not the only way. There are also implementation in Python and Ruby. Attaching roles to objects is somewhat more straight forward in Ruby, where it is done by mixing a module specifying the a role into a domain object at runtime. With IronRuby maturing nicely, that could easily turn out to be the nicer DCI implementation for .NET.