Pages

Monday, December 20, 2010

Options for DCI on .NET

Here's a short summary of the technical/language options for doing DCI on the .NET platform. The list below is loosely ordered in order of increasing "mainstreamness" - according to me :-).
The list notes the language, how roles and contexts are handled and how the solution is run on .NET.

  • Scala "cross-compiled" to MSIL/CLR:
    In Scala contexts are simply objects instantiated at runtime from context classes. The contexts have the responsibility of instantiating data objects and mapping roles to them. Roles are traits that are mixed into data objects at instantiation time. So typically the contexts receive ids for data objects, pull the corresponding data from e.g. a database, and instantiate the data object with a role trait mixed in. In Scala this is all strongly typed and looks like this:

    class MoneyTransferContext(sourceAccountId: int,
                                sinkAccountId: int){
       val source =
            new SavingsAccount(sourceAccountId) with TransferMoneySource
       val sink =
             new CheckingAccount(sinkAccountId) with TransferMoneySink
       def execute = {
         source.deposit(100000)
         source.transferTo(200, sink)
         }
    }

    To run this in a .NET setting the Scala code must "cross-compiled" to MSIL, and must use the .NET standard library along with the Scala APIs. For details on this see my earlier post, or the introduction on the Scala language web site.

  • Python running on IronPython:
    In Python contexts are objects, that take the responsibility of mixing roles into data objects dynamically. The data objects can be instantiated outside the context or inside the context depending on taste. When the context is done the roles can be yanked back out of the data objects. I dont know a whole lot of Python, so I wont go into details, but refer you to Serge Beaumonts sample.
    To run this on .NET use IronPython.
  • Ruby running on IronRuby:
    In Ruby contexts are also objects, that take the responsibility of mixing roles into data objects dynamically. Data objects can be instantiated inside the context or outside the context. Roles in Ruby are implemented as modules, which using the Ruby standard library can be mixed into any Ruby object. The Ruby code looks like this:
    class TransferMoneyContext
       attr_reader :source_account, :destination_account, :amount
       include ContextAccessor
       
       def self.execute(amt, source_account_id, destination_account_id)
         TransferMoneyContext.new(amt, source_account_id,
                                  destination_account_id).execute
       end
     
       def initialize(amt, source_account_id, destination_account_id)
         @source_account = Account.find(source_account_id)
         @source_account.extend MoneySource
         @destination_account = Account.find(destination_account_id)
         @amount = amt
       end
    
       def execute
         in_context do
           source_account.transfer_out
         end
       end
    end

    To run this on .NET just use IronRuby. It runs fine out of the box.

  • C# and Dynamic:
    Using the dynamic features of C#, contexts are objects that receive either data objects or IDs of data objects. In case of an ID the context instantiates the data object and populates it based on the ID and some data source. In both cases the data object has a role reference which will point to an expando object, into which the roles methods are injected. The code looks roughly like this:
    dynamic transfer = new ExpandoObject();
     transfer.Transfer = (Action)
      ((source, sink, amount) =>{ 
        dynamic mSource = source;
        dynamic mSink = sink;
        mSource.DecreaseBalance(amount);
        mSink.IncreaseBalance(amount);});
     Account sourceAcct = new Account { CashBalance = 100m };
     sourceAcct.Role = transfer;
     Account sinkAcct = new Account { CashBalance = 50m };
     sourceAcct.Role.Transfer(sourceAcct,sinkAcct,10m);
    Running on .NET? Well, it's C#, so just do it.

  • C# and extention methods:
    Using C# extension methods for roles contexts are objects that receive either data objects or IDs of data objects. In either case the context maps the data objects to roles. Roles are implemented as static classes containing extension methods for role interfaces. Data objects capable of playing a given role implement that role interface. The code looks like this:
    public class TransferMoneyContext { public TransferMoneySource Source { get; private set; } public TransferMoneySink Sink { get; private set; } public decimal Amount { get; private set; } public TransferMoneyContext(TransferMoneySource source, TransferMoneySink sink, decimal amount) { Source = source; Sink = sink; Amount = amount; } public void Execute() { Source.TransferTo(Sink, Amount); } } public interface TransferMoneySink { void Deposit(decimal amount); void Log(string message); } public interface TransferMoneySource { decimal Balance { get; } void Withdraw(decimal amount); void Log(string message); } public static class TransferMoneySourceTrait { public static void TransferTo(this TransferMoneySource self, TransferMoneySink recipient, decimal amount) { // The implementation of the use case if (self.Balance < amount { throw new ApplicationException("insufficient funds"); } self.Withdraw(amount); self.Log("Withdrawing " + amount); recipient.Deposit(amount); recipient.Log("Depositing " + amount); } }

    How to run this on .NET is ... well ... pretty obvious :-)

9 comments:

  1. Hi,

    what do you think about this?

    public class Account
    {
    public dynamic Role { get; set; }

    public decimal Balance { get; set; }

    public void IncreaseBalance(decimal amount)
    {
    this.Balance += amount;
    }

    public void DecreaseBalance(decimal amount)
    {
    this.Balance -= amount;
    }

    }

    public static class MoneySourceRole
    {
    public static void TransferTo(this Account source,Account sink, decimal amount)
    {
    source.Role = (Action)(() =>
    {
    source.IncreaseBalance(amount);
    sink.DecreaseBalance(amount);
    }
    );

    Action action = (Action)source.Role;
    action();
    }
    }

    public class AppConsole
    {
    public static void Main(string[] args)
    {
    var account1 = new Account { Balance = 10m };
    var account2 = new Account { Balance = 30m };

    account2.TransferTo(account1, 5m);

    Console.WriteLine(account1.Balance);
    Console.WriteLine(account2.Balance);


    Console.ReadLine();
    }
    }
    }

    ReplyDelete
  2. @ryzam:
    Well, I see a couple of issues:
    Firstly, the role is implemented as extension methods on the account type, which is a concrete type, thus the the role only be applied to that single domain type (and its inheritors). You want to be able to have a full many-to-many relationship between roles and domain types.
    Seondly, the I dont really see the point of using a dynamic field for the role implementation. Could you maybe, explain this choice?

    ReplyDelete
  3. but what if the concrete type is a value object where you can use anywhere in your entity?

    ReplyDelete
  4. @ryzam:
    I might be misunderstanding you, but the role is to be assigned to an entity, not a value object, because the entities are the objects that appear as actors in the use cases. The value object are just values.

    ReplyDelete
  5. ok i think this one much more easier

    http://www.pastie.org/1436040

    ReplyDelete
  6. That's an interesting approach. What is the rationale behind the design?
    To be honest it feels a bit convoluted to me. E.g. the indirection level in calling the role code seem somewhat complicated; basically the role code is assigned to objects and then executed immediately, so why not a more direct approach?
    And is it enough to have just one method with the action signature on the role? -In the general case I dont think it is.

    ReplyDelete
  7. For an option very low in "mainstreamness", you can take a look at NRoles, a post-compiler I created to enable roles in C#:

    http://codecrafter.blogspot.com/2011/05/nroles-experiment-with-roles-in-c.html

    ReplyDelete
  8. @jordao: Thanks for the pointer. You may find http://remix.codeplex.com/ which a .NET mixin implementation, that I guess could also be used for doing DCI.

    ReplyDelete
  9. This is my implementation of the DCI in C# using the decorator pattern. What do you guys think.

    http://pastie.org/6403027

    ReplyDelete