Jan has posted 1 posts at DZone. View Full User Profile

DCI in AspectJ

03.16.2011
| 6193 views |
  • submit to reddit

A couple months ago I came across a DCI (Data, Context, Interaction) concept which I found quite interesting. Since Java does not support traits an implementation of the DCI in Java is a bit awkward. I managed to find a few implementations: Simple Java DCI Implementation based on direct usage of dynamic proxies, an implementation based on Qi4J framework and then lately an another using DCI Tools for Java library. Both Qi4J and DCI Tools for Java in turn rely on reflection and dynamic proxies in particular. When you look at the code you will find that every approach involves a lot of 'scaffolding' code which helps to overcome the Java shortcoming. For example let's have a look at Qi4J sample:

SingletonAssembler assembler = new SingletonAssembler() {
public void assemble(ModuleAssembly module) throws AssemblyException {
module.addEntities(TaskEntity.class, UserEntity.class);
module.addServices(MemoryEntityStoreService.class, UuidIdentityGeneratorService.class);
}
};

UnitOfWork uow = assembler.unitOfWorkFactory().newUnitOfWork();

TaskEntity task = uow.newEntity(TaskEntity.class);

EntityBuilder<UserEntity> builder = uow.newEntityBuilder(UserEntity.class);
builder.instanceFor(AssigneeData.class).name().set("Rickard");
UserEntity user = builder.newInstance();

{
InboxContext inbox = new InboxContext(user, user);
inbox.assign(task);
}

uow.complete();

At the first sight it is not very clear what is going on here. Basically a Task is assigned to a User with the name Rickard within the Inbox context. Although the readability of the code can be improved by using factories for instantiating the entities you will probably end up writing a lot of boilerplate code inside the factories. Also as mentioned before all approaches heavily rely on reflection which may affect performance of the application. And at last the entities are not regular Java objects so for example serialization of the entities or integration with Spring framework is not straightforward. Considering this I started looking for different ways how to implement DCI and I found an idea of implementing roles as aspects appealing.

AspectJ Implementation

In the next section I'll show an example how to implement DCI in AspectJ. The example is a rewritten example of DCI implementation in Qi4J which can be downloaded from here and is well described in Rickard Öberg's presentation. The example shows a User and a Project having a Task assigned in an Inbox context. The User, the Project and the Task are entities where the User can have several Tasks assigned. The assigned Tasks can be a part of the Project, too. Now you have probably realized that the entities play roles. In this case the User has an Assignee role which represents someone who will work on a Task (or an assignment in general). It also has an Assignments role i.e. the User can have multiple Tasks assigned. On the other hand the Task has just an Assignable role so it can be assigned to an entity, in this case to the User or the Project. And similarly to the User entity, the Project plays the Assignments role as well so it can hold assignments - Tasks in this case.

Let's have a look at the code. The roles are represented by interfaces.

public interface Assignments {

void assign(Assignable assignable, Assignee assignee);

List<Assignable> assignments();
}

public interface Assignable {

void assignTo(Assignee assignee);
}

public interface Assignee {

String assigneeName();
}

The entities are represented by annotated classes.

@AssignmentsRole
public class Project {
}

@AssignableRole
public class Task {
}

@AssignmentsRole
@AssigneeRole
public class User {

public User(String name) {
setName(name);
}
}

The code above is the same as

public class User implements Assignments, Assignee {
}

where the interfaces are not implemented by the User class but by aspects. (I personally prefer the annotations as a hint for the AspectJ compiler rather than relying on the interface implementation. This gives a programmer reading the code a better visual clue that the interfaces are implemented by the aspects.) The annotations serve as markers so the AspectJ compiler knows what aspects it should apply on the class. The annotations themselves are just simple annotations with default retention policy (RetentionPolicy.CLASS) and which can be applied on class declarations.

@Target(ElementType.TYPE)
public @interface AssigneeRole {
}

Now let's move to the role implementations. As mentioned before they are implemented as aspects.

aspect AssignableImpl {

declare parents: (@AssignableRole *) implements Assignable;

private Assignee Assignable.assignee;

public void Assignable.assignTo(Assignee assignee) {
// No object schizophrenia - this reference is to the entity itself.
this.assignee = assignee;
System.out.println(assignee.assigneeName());
}
}

The declare parents part says the Assignable interface will be implemented by any class marked by the @AssignableRole annotation. Below declare parents is the actual implementation of the interface. The assignee variable and the assignTo method will become members of the entity(ies) marked by the @AssignableRole annotation.

The implementation of the other roles follows the same pattern.

aspect AssignmentsImpl {

declare parents: (@AssignmentsRole *) implements Assignments;

private List<Assignable> Assignments.assignments = new ArrayList<Assignable>();

public void Assignments.assign(Assignable assignable, Assignee assignee) {
assignable.assignTo(assignee);
assignments.add(assignable);
}

public List<Assignable> Assignments.assignments() {
return assignments;
}
}

The Inbox context implementation where the assignment is being made is below.

public class InboxContext {

private Assignments assignments;
private Assignee assignee;

public InboxContext(Assignments assignments, Assignee assignee) {
this.assignments = assignments;
this.assignee = assignee;
}

public void assign(Assignable assignable) {
assignments.assign(assignable, assignee);
}
}

And now let's put everything together.

Task task = new Task();
User user = new User("Rickard");

InboxContext inbox = new InboxContext(user, user);
inbox.assign(task);

System.out.println("Nr of assignments: " + user.assignments().size());

Project project = new Project();
Task task2 = new Task();

inbox = new InboxContext(project, user);
inbox.assign(task2);

System.out.println("Nr of assignments: " + project.assignments().size());

I find this much cleaner that the Qi4J code. Also the entities are basically first class Java citizens so they can be for example serialized the same way as regular classes (see the serialization example in the source code) or used as Spring beans. Below is a rewritten code using the Spring framework.

The Spring configuration file spring.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="userRickard" class="net.sourceforge.dciaspectj.example1.entity.User">
<constructor-arg index="0" value="Rickard" />
</bean>

<bean id="project" class="net.sourceforge.dciaspectj.example1.entity.Project" />

<bean id="task1" class="net.sourceforge.dciaspectj.example1.entity.Task" />

<bean id="task2" class="net.sourceforge.dciaspectj.example1.entity.Task" />

<bean id="userInbox" class="net.sourceforge.dciaspectj.example1.context.InboxContext">
<constructor-arg index="0" ref="userRickard" />
<constructor-arg index="1" ref="userRickard" />
</bean>

<bean id="projectInbox" class="net.sourceforge.dciaspectj.example1.context.InboxContext">
<constructor-arg index="0" ref="project" />
<constructor-arg index="1" ref="userRickard" />
</bean>
</beans>

The code loads the application context defined in spring.xml. The entities and the InboxContext are then looked up and obtained from this Spring application context.

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring.xml");

Task task = (Task) ctx.getBean("task1");
InboxContext inbox = (InboxContext) ctx.getBean("userInbox");

inbox.assign(task);

User user = (User) ctx.getBean("userRickard");
System.out.println("Nr of assignments: " + user.assignments().size());

Task task2 = (Task) ctx.getBean("task2");
inbox = (InboxContext) ctx.getBean("projectInbox");

inbox.assign(task2);

Project project = (Project) ctx.getBean("project");
System.out.println("Nr of assignments: " + project.assignments().size());

 

Role Polymorphism

I am not sure if subtyping of the roles is still within the scope of the original DCI idea but you can definitely experiment with it. For example it can be useful to have a CompositeAssignee role which would represent a group of Asignees and at the same time it would be an Assignee itself. A good candidate for this role would be a group of Users forming a team.

The CompositeAssignee extends the Assignee interface.

public interface CompositeAssignee extends Assignee {

List<Assignee> assignees();
}

The role annotation and implementation of the role is below.

@Target(ElementType.TYPE)
public @interface CompositeAssigneeRole {
}

aspect CompositeAssigneeImpl {
declare parents: (@CompositeAssigneeRole *) implements CompositeAssignee;

private List<Assignee> CompositeAssignee.assignees = new ArrayList<Assignee>();

// You can also override a method if needed.
public String CompositeAssignee.assigneeName() {
StringBuilder name = new StringBuilder(super.assigneeName());
name.append(":");
for (Assignee assignee : assignees) {
name.append(" ");
name.append(assignee.assigneeName());
}
return name.toString();
}

public List<Assignee> CompositeAssignee.assignees() {
return new ArrayList<Assignee>(assignees);
}

public void CompositeAssignee.addAssignee(Assignee assignee) {
assignees.add(assignee);
}
}

The Group entity has then the CompositeAssignee role.

 @AssignmentsRole
@CompositeAssigneeRole
public class Group {

public Group(String name) {
setAssigneeName(name);
}
}

 And here is the usage:

Task task = new Task();
Group team = new Group("DCI Team");
team.addAssignee(new User("Rickard"));
team.addAssignee(new User("Jim"));
team.addAssignee(new User("Trygve"));

InboxContext inbox = new InboxContext(team, team);
inbox.assign(task);

System.out.println("Nr of assignments: " + team.assignments().size());

You can download the source code from here. The example described in this article is in the example1 folder. A rewritten example described in Simple Java DCI Implementation can be found in the example2 folder.

Resources

Published at DZone with permission of its author, Jan Gaspar.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Rob Williams replied on Sat, 2011/03/19 - 9:38am

Interesting. Have you seen the Aspects book by Ivar Jacobsen? It takes a slightly different approach to composability (first separating concerns then recombining them).

I like how clean the code is at the end, but implementing Composite with an annotation seems loopy to me. I do believe that Composite is absolutely everywhere, but working with it is very difficult. For instance, as soon as you had to specialize either side of the composed relations here, you would have to figure out how to extend the composite. That's when things get really complicated.

I was really enjoying AOP, but the eclipse plugin was so buggy, then they decided not to support Helios until M8. Makes it a burden to drag around as part of a core stack.

Jan Gaspar replied on Sun, 2011/03/20 - 7:41pm in response to: Rob Williams

No, unfortunatelly I haven't read this book. Regarding the composite - the intention was to show that a role can be extended ... it doesn't have to be a composite at all. CompositeAssignee seemed to me as a good example in this context. If you mean by 'a composite' an entity in general which is composed out of the roles then you can 'implement' interfaces - not using annotations - and let the pointcuts rely on interface implementation. However it doesn't make it any better if the composing itself is an issue. Not sure how would I implement DCI differently.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.