Raoul Duke wrote:
http://www.artima.com/articles/dci_vision.html

(well, i kinda liked it.)

sincerely.

I've been part of the group that discussed this topic and article, and will be writing a followup showing how to do DCI using Qi4j.

I'm currently working on the StreamFlow workflow app, and decided to try using DCI concepts for an interaction, "Create new task and assign to Inbox". Here's my current version:
/**
 * New Task Interaction:
 * 1) Send Task to TaskReceiver
 * 2) Create Domain Event for the transfer
 */
@Mixins(NewTaskInteraction.NewTaskMixin.class)
public interface NewTaskInteraction
   extends Interaction<Object>, NewTaskContext
{
   class NewTaskMixin
      extends InteractionMixin<Object, NewTaskContext>
   {
      @Service
      ApplicationEvents events;

      public Object call() throws Exception
      {
         state.receiver()._().receiveTask(state.task()._());

events.logEvent(new ApplicationEvent(ApplicationCategory.TASK, EventType.CREATED, state.task()._(), new Date()));

         return null;
      }
   }
}

interface NewTaskContext
{
   Property<Task> task();
   Property<TaskReceiver> receiver();
}

@Concerns(UsecaseUnitOfWorkConcern.class)
public interface Interaction<RESULT>
   extends Composite, Callable<RESULT>
{
   public abstract class InteractionMixin<RESULT, INTERACTION>
      implements Callable<RESULT>
   {
      @This INTERACTION state;
   }
}
---
"Interaction" is a base composite which specifies that the interaction should implement Callable, which is what is used to trigger the interaction. It also specifies a base class for interaction mixins, which always should implement Callable, and always has access to the context state. I use generics to make everything type safe.

In the code, when I create a new UI dialog to create a task I will at the same time instantiate this Composite. The dialog will be used to set the properties in the context, i.e. create the task itself and then choose what TaskReceiver (e.g. an "Inbox" or personal "Workspace") to send it to. When the user clicks "Ok" in the dialog the call() method is invoked, which will cause a new nested UnitOfWork to be created (this is done by a concern as it is cross-cutting for all interactions), and then the task is handed to the receiver and a log event is published. If anything goes wrong the nested unit of work is discarded, and the UI can show an error dialog with the message and either abort or show the "new task dialog" again.

The "TaskReceiver" interface is a usecase-specific interface which only has ONE method receiveTask(). This role is implemented by Inboxes (if I want to send a task to someone else) as well as the local Workspace (if I want to give the task to myself). The key is that I am NOT binding the interaction to either the specific Inbox or Workspace interfaces, which would give me very difficult code to manage: the context would have to have two properties, one for each type, and then an if-statement in the execution depending on which type was chosen.

This example shows (to me anyway) how useful DCI can be when interactions are implemented as first-class concepts rather than just as methods in a service. Yes, it may seem verbose at first to have one composite per usecase, but I think it is worth it since everything I need for the usecase is gathered in one place, rather than spread out. It gives me a very specific place to look when I want to change it.

/Rickard

_______________________________________________
qi4j-dev mailing list
[email protected]
http://lists.ops4j.org/mailman/listinfo/qi4j-dev

Reply via email to