NAME
    DCI - Collection of utilities for writing perl code that fits the DCI
    methodology.

DESCRIPTION
    Defines the DCI concepts of Context DCI::Context and Role DCI::Cast.

    The DCI concept of a 'role' differs from the concept of roles as defined
    by Moose. Because of this the term 'Cast' is used by DCI to refer to DCI
    Roles. This will hopefulyl avoid some confusion.

SYNOPSYS
    Here we will implement a complete algorthm including Data classes, a
    Context class, and Casts. We will implement a banking transfer as that
    is a common example used to explain DCI.

    This example is implemented in the "t/bank.t" test if you wish to see it
    in action.

  THE DATA CLASSES
   Account
    This is an implementation of a bank account that follows the DCI
    principal that Data objects should be fairly dumb.

        package Account;
        use strict;
        use warnings;

        sub new {
            my $class = shift;
            my ($balance) = @_;

            return bless( \$balance, $class );
        }

        sub add_balance {
            my $self = shift;
            my ( $delta ) = @_;
            $$self += $delta;
        }

        sub subtract_balance {
            my $self = shift;
            my ( $delta ) = @_;
            $$self -= $delta;
        }

        sub get_balance {
            my $self = shift;
            return $$self;
        }

   Log
    This is a package for a log or recipt class, also dumb.

        package Log;
        use strict;
        use warnings;

        sub new {
            my $class = shift;
            return bless( [], $class );
        }

        sub record {
            my $self = shift;
            my ($line) = @_;
            chomp( $line );
            push @$self => $line;
        }

  THE CONTEXT (Use-Case)
    This is the package for our use case, a transfer between accounts.

        package Transfer;
        use strict;
        use warnings;

        use DCI::Context;

        # Define the roles
        cast from_acct => 'Transfer::FromAccount',
             dest_acct => 'Transfer::DestAccount',
             # Roles with no cast class will be used as-is
             recipt    => undef,
             ammount   => undef;

        # Create a sugar function that is exported when this use-case is imported.
        sugar 'account_transfer';

        sub start_transaction {
            my $self = shift;
            $self->recipt->record( "Transaction started" );
            # ... Stuff to record current state in case of issue
        }

        sub rollback_transaction {
            my $self = shift;
            my ( $error ) = @_;
            $self->recipt->record( "Transaction aborted: $error" );
            # ... Stuff to restore previous state
        }

        sub commit_transaction {
            my $self = shift;
            $self->recipt->record( "Transaction completed" );
            # ... Stuff to finalize state
        }

        sub run {
            my $self = shift;

            $self->start_transaction;

            my $success = eval {
                $self->from_acct->verify_funds();
                $self->from_acct->withdrawl();
                $self->dest_acct->deposit();
                1;
            };

            if ( $success ) {
                $self->commit_transaction;
            }
            else {
                my $error = $@;
                $self->rollback_transaction( $error );
            }
        }

  THE ROLES (CAST)
    We define 3 roles, from_acct, dest_acct, and recipt.

   Transfer::FromAccount
        package Transfer::FromAccount;
        use strict;
        use warnings;

        use DCI::Cast;

        # Require that core class is an Account object.
        restrict_core qw/Account/;

        sub verify_funds {
            my $self = shift;
            my $ammount = $self->CONTEXT->ammount;
            die "Origin account has insufficient funds\n" unless $ammount <= $self->get_balance;
        }

        sub withdrawl {
            my $self = shift;
            my $ammount = $self->CONTEXT->ammount;
            $self->subtract_balance( $ammount );
            $self->CONTEXT->recipt->record( "$ammount removed from origin account" );
        }

   Transfer::DestAccount
        package Transfer::DestAccount;
        use strict;
        use warnings;

        use DCI::Cast;

        restrict_core qw/Account/;

        sub deposit {
            my $self = shift;
            my $ammount = $self->CONTEXT->ammount;
            $self->add_balance( $ammount );
            $self->CONTEXT->recipt->record( "$ammount added to destination account" );
        }

  PUTTING IT ALL TOGETHER
   USING THE SUGAR FUNCTION
        # This will import the 'account_transfer()' function.

        use Transfer;
        my $from = Account->new( 1000 );
        my $to = Account->new( 100 );
        my $log = Log->new();

        Transfer->import();

        account_transfer(
            from_acct => $from,
            dest_acct => $to,
            recipt    => $log,
            ammount   => 500,
        );

        is( $from->get_balance, 500, "500 removed from origin account (sugar)" );
        is( $to->get_balance,   600, "500 added to dest account (sugar)" );

        is_deeply(
            $log,
            [
                'Transaction started',
                '500 removed from origin account',
                '500 added to destination account',
                'Transaction completed'
            ],
            "Recipt is accurate (sugar)"
        );

   SUCCESSFUL TRANSFER
        my $from = Account->new( 1000 );
        my $to = Account->new( 100 );
        my $log = Log->new();

        my $context = Transfer->new(
            from_acct => $from,
            dest_acct => $to,
            recipt    => $log,
            ammount   => 500,
        );

        $context->run();

        is( $from->get_balance, 500, "500 removed from origin account" );
        is( $to->get_balance,   600, "500 added to dest account" );

        is_deeply(
            $log,
            [
                'Transaction started',
                '500 removed from origin account',
                '500 added to destination account',
                'Transaction completed'
            ],
            "Recipt is accurate"
        );

   FAILED TRANSFER
        my $from = Account->new( 100 );
        my $to = Account->new( 100 );
        my $log = Log->new();

        my $context = Transfer->new(
            from_acct => $from,
            dest_acct => $to,
            recipt    => $log,
            ammount   => 500,
        );

        $context->run();

        is( $from->get_balance, 100, "Transaction failed, balance uneffected" );
        is( $to->get_balance,   100, "Transaction failed, balance uneffected" );

        is_deeply(
            $log,
            [
                'Transaction started',
                'Transaction aborted: Origin account has insufficient funds',
            ],
            "Recipt is accurate"
        );

SEE ALSO
    DCI::Context
    DCI::Cast

DCI Overview
    DCI Stands for Data, Context, Interactions. It attempts to solve the
    problems of OOP. DCI was designed and proposed by the same guy that
    created the MVC concept that has become hugely successful. The key to
    DCI is a seperation of concepts:

    Data, what the system is.
    Context, A use case.
    Interactions, How objects behave within a context.

    The idea is to create data objects that have no algorithm or business
    logic at all. These would be very simple "dumb" objects. A good example
    would be ORM objects that get used in many contexts. The key is to only
    add methods which make sence without any knowledge of the business logic
    or use cases in which they will participate.

    Once you have your data objects you then move on to a context. A context
    itself can be thought of as an object. A context implements a use case,
    which could be an encapsulated bit of business logic, or an algorithm.
    The use case object would keep track of objects necessary to complete
    the task. The context keeps track of these items by the concept of what
    role they will play.

    In DCI the concept of a role only superficially resembles roles as they
    are implemented by Moose. In Moose a role is essentially a mixin with
    methods that get injected into a class as soon as the role is used, and
    then they remain present for the life of the class. In DCI roles can
    also be thought of as mixins, however the methods they contain should
    only be present in your data object when it is used in context.

    To wrap up:

    Data objects
        Such as objects in an ORM, should not contain business or algorithm
        logic. Only methods that make sence without any context belong in
        the data objects.

    Context objects
        Implement an algorithm by defining roles, assigning data objects to
        roles, and then kicking off the interactions between the roles.

    Roles
        Collections of methods used for interactions between objects in a
        specific use-case (context)

DCI RESOURCES
    <http://www.artima.com/articles/dci_vision.html>
    <http://en.wikipedia.org/wiki/Data,_Context_and_Interaction>
    <https://sites.google.com/a/gertrudandcope.com/www/thedciarchitecture>
    <http://oredev.org/videos/dci--re-thinking-the-foundations-of-oo>

AUTHORS
    Chad Granum exodist7@gmail.com

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.

