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