Hi guys,

On the DCI mailing list, there has been a lot of discussion about how to actually implement roles, and what is the "proper way" and what is the "practical way", and so on. Lately I came up with a variant that seems to fulfill all the semantical and syntactical criteria, with the only problem being that the roles are not actually part of the object, but instead are implemented as wrappers. So, I wanted to run this by you guys and get your views on this.

Here's the old way: an Entity would have Data, i.e. Properties and Associations, and with methods to modify these formulated as imperative Commands (i.e. "changeFoo"). These can internally be handled by just doing it (i.e. check if command is ok, and if yes, apply to state), or it can be done by using event sourcing (i.e. do command check and then call event method that in turn does the state change). Here's an example from Streamflow:
@Mixins(Groups.Mixin.class)
public interface Groups
{
   Group createGroup( String name );

   ... methods removed for brevity ...

   interface Data
   {
      @Aggregated
      ManyAssociation<Group> groups();
   }

   interface Events
   {
      Group createdGroup( @Optional DomainEvent event, String id );

      void addedGroup( @Optional DomainEvent event, Group group );

      void removedGroup( @Optional DomainEvent event, Group group );
   }

   class Mixin
         implements Groups, Events
   {
      @This Data data;

      @Service
      IdentityGenerator idGen;

      @Structure
      Module module;

      public Group createGroup( String name )
      {
Group group = createdGroup(null, idGen.generate(GroupEntity.class));
         group.changeDescription( name );
         addGroup(group);
         return group;
      }

      public Group createdGroup(@Optional DomainEvent event, String id)
      {
return module.unitOfWorkFactory().currentUnitOfWork().newEntity(Group.class, id);
      }
      .. the rest of the methods removed for brevity ...
   }
}
---
The above is then extended by an entity interface that needs "Group management", in my case an OrganizationalUnitEntity.

So once you have a "Groups" entity, you can invoke the commands directly. With DCI, the "createGroup" method would only be called from a GroupsContext where the interaction exist.

The new way: since GroupsContext is basically the only place from which you would do this, and it represents a particular usecase, the "createGroup" method actually belongs in a role in the context, which is only valid *from within that context*. So, the way to do this is to implement GroupsContext as a class and then do the Groups commands thing as an inner class that wraps a Groups object that has Events+Data:
public class GroupsContext
{
   // Injection
   @Structure
   Module module;

   GroupsAdmin groups;

   public GroupsContext()
   {
      groups = new GroupsAdmin(); // Create wrapper
   }

   public GroupsContext rebind(Groups groups)
   {
      this.groups.bind(groups); // Bind wrapper
      return this;
   }

   public Iterable<Group> index()
   {
      return groups.index();
   }

   public Group creategroup( StringValue name )
   {
      return groups.createGroup( name.string().get() );
   }

   class GroupsAdmin
      extends Role<Groups>
   {
      Iterable<Group> index()
      {
        return self.groups();
      }

      Group createGroup(String name)
      {
Group group = self.createdGroup(null, module.serviceFinder().<IdentityGenerator>findService(IdentityGenerator.class).get().generate(GroupEntity.class));
         group.changeDescription(name);
         self().addedGroup(null, group);
         return group;
      }
   }
}
---
The way to use this in client code would be:
new GroupsContext().rebind(groups).createGroup(newName);

The benefits with this solution are:
* You can create any number of contexts and roles that uses the basic data/event methods, and don't have to worry with method clashing (major issue without this solution). * Role code can access members of the Context that wraps it, e.g. "module". If there were more bound objects, those could be accessed directly instead of having them be passed as method arguments to role methods. * Easier to see the algorithms as they are directly in the context, rather than having to go one step down into some external mixin.
* Roles are only visible/usable from within the context that defines them.
* Very clear what the dependencies of the context are.
* Invisible from outside that DCI is being used.

The drawbacks I can see are:
* Since the roles are wrappers you have to think about what to pass around: wrapper or self() (will almost always be self() since wrapper class is unknown outside of context) * Right now cannot use concerns/sideeffects/constraints on inner classes. Can still use on context interaction if implemented as transient though.

I'm sure there are more benefits and drawbacks, but this is what I can see right now. If anyone has other ideas, that would be great. IF this solution is acceptable, then that changes quite a bit what the typical usage pattern of Qi4j in a DCI setting would be, so it is kind important. It also makes it easier to use DCI outside of Qi4j, since there's nothing really in the above that requires Qi4j, from a pattern point of view.

WDYT?

/Rickard

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

Reply via email to