Using AspectJ and Annotations to document framework pollution in the domain model

This blog post will show you how to use Aspectj and Annotations in Java to avoid that certain framework artifacts ‘pollute’ your code and people start calling methods that you really don’t want when you thought out your design.

A concrete example for this if you want to create immutable objects. Value Objects in Domain Driven Design should be like this. You create a constructor for all your object needs, mark all fields final and add some public getters. But now you want to send this object to a Flex client using BlazeDS. You need a default constructor and getters for all properties. Your immutable object, just became highly mutable,  just because the framework you use needs this. Same can be said for Hibernate or Coherence that need a default constructor.

First, we will create an annotation that allows to mark methods and constructors as only being needed because a framework we use requires it:

@Retention(RetentionPolicy.RUNTIME)
public @interface FrameworkArtifact 
{
    Framework[] value();
}

Framework is an enum that contains the actual frameworks we want to use:

public enum Framework
{
	BLAZEDS,
	HIBERNATE
}

This is our example domain object, which we liked to have made immutable, but we cannot due to Hibernate and Blazeds:

public class DomainObject
{
	private int id;
	private String name;

	@FrameworkArtifact({Framework.HIBERNATE, Framework.BLAZEDS})
	public DomainObject()
	{
	}

	public DomainObject(int id, String name)
	{
		this.id = id;
		this.name = name;
	}

	public int getId()
	{
		return id;
	}

	@FrameworkArtifact(Framework.BLAZEDS)
	public void setId(int id)
	{
		this.id = id;
	}

	public String getName()
	{
		return name;
	}

	@FrameworkArtifact(Framework.BLAZEDS)
	public void setName(String name)
	{
		this.name = name;
	}
}

This already gives us some documentation on why some methods are there and they are probably shown as not used by your editor. If you use IntelliJ IDEA, you can tell it to not show those methods as not used if they have this annotation.

We want to go a step further and have the compiler signal us if “normal” code calls these methods, because we don’t want that. It is quite easy using AspectJ:

public aspect EnforceFrameworkArtifactUsage
{
	declare error : call( @FrameworkArtifact * * (..)) || call( @FrameworkArtifact new(..) )
	: "You should not call a FrameworkArtifact directly: {joinpoint.signature} ";
}

This aspect tells the aspectj compiler to fail if there is something that calls a method that is annotated with @FrameworkArtifact or a constructor. Suppose this little main class:

public class Main
{
	public static void main(String[] args)
	{
		DomainObject domainObject = new DomainObject(1, "myName");
		String name = domainObject.getName();
		System.out.println("name = " + name);

		DomainObject domainObject2 = new DomainObject();
		domainObject2.setId(2);
	}
}

The first part is usage we want to allow, the second part is what we do not want to allow. If we compile, the aspect will match those calls and report a compilation error (this is done using maven here):

[INFO] Compiler errors:
error at DomainObject domainObject2 = new DomainObject();
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/home/wdb/Personal/projects/framework-artifacts/src/main/java/org/wimdeblauwe/fa/Main.java:13:0::0 You should not call a FrameworkArtifact directly: void org.wimdeblauwe.fa.domain.DomainObject.()
see also: /home/wdb/Personal/projects/framework-artifacts/src/main/aspect/org/wimdeblauwe/fa/architecture/EnforceFrameworkArtifactUsage.aj:8::0
error at domainObject2.setId(2);
^^^^^^^^^^^^^^^^^^^^^^
/home/wdb/Personal/projects/framework-artifacts/src/main/java/org/wimdeblauwe/fa/Main.java:14:0::0 You should not call a FrameworkArtifact directly: void org.wimdeblauwe.fa.domain.DomainObject.setId(int)
see also: /home/wdb/Personal/projects/framework-artifacts/src/main/aspect/org/wimdeblauwe/fa/architecture/EnforceFrameworkArtifactUsage.aj:8::0

It is a pity that the error message does not show the actual framework that is involved. AFAIK, this is not something you can do with declare error. If you really want this, you can change the aspect to use a before advice. This will not fail the build, but will only warn you when the actual call happens in your code. Advantage is that you have more freedom to do what you want (print something, throw exception, …), but the biggest drawback is ofcourse that it is at runtime, not at compile time.

This is the aspect using the before advice:

public aspect EnforceFrameworkArtifactUsage
{
	before(FrameworkArtifact fa) : 
            (call( @FrameworkArtifact * * (..)) || call( @FrameworkArtifact new(..) )) && @annotation( fa )
            {
		System.out.println( "You should not call a FrameworkArtifact directly: " + thisJoinPoint.getSignature() 
                                          + " is only there for " + Arrays.asList(fa.value())  
                                          + ". It is called from: " + thisEnclosingJoinPointStaticPart.getSourceLocation() );
	}

As said, compilation will not fail now. However, if you run Main.java, it will print the following:

You should not call a FrameworkArtifact directly: org.wimdeblauwe.fa.domain.DomainObject() is only there for [HIBERNATE, BLAZEDS]. It is called from: Main.java:7
You should not call a FrameworkArtifact directly: void org.wimdeblauwe.fa.domain.DomainObject.setId(int) is only there for [BLAZEDS]. It is called from: Main.java:7

Advertisements

2 thoughts on “Using AspectJ and Annotations to document framework pollution in the domain model

  1. For a first post, this is a good post! The goal -making the framework pollution explicit, and preventing unexpected use- is a brilliant idea, and the means -annotations and AspectJ- is an easy solution. Did this idea originate during a Domain-Driven Design immersion course? Were there other great ideas like this?

    Also, as you probably know, an alternative to AspectJ would be the pluggable annotation processing (Java 6) or the annotation processing tool (Java 5), at compile time, but they would both require more coding.

    Hope to read more posts like that soon.
    Cheers

    • Hi Cyrille,

      no, we are using something similar in the project at work. I just mentioned it on the DDD immersion course. The teacher wanted to know more about it and asked if I had a blog entry about it. I did not at the time and that is why I did this entry.

      regards,

      Wim

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s