First Tryst with Domain Aspects
I am tired of looking at aspects for logging, tracing, auditing and profiling an application. With the new AspectJ 5 and Spring integration, you can do all sorts of DI and wiring on aspects using the most popular IoC container. Spring's own
@Transactional
and @Configurable
are great examples of AOP under the hoods. However, I always kept on asking Show me the Domain Aspects, since I always thought that in order to make aspects a first class citizen in modeling enterprise applications, it has to participate in the domain model.In one of our applications, we had a strategy for price calculation which worked with the usual model of injecting the implementation through the Spring container.
<beans>
<bean id="defaultStrategy" class="org.dg.domain.DefaultPricing"/>
<bean id="priceCalculation" class="org.dg.domain.PriceCalculation">
<property name="strategy">
<ref bean="defaultStrategy"/>
</property>
</bean>
</beans>
Things worked like a charm, till in one of the deployments, the client came back with the demand for implementing strategy failovers. The default implementation will continue to work as the base case, while in the event of failures, we need to iterate over a collection of strategies till one gets back with a valid result. Being a one of a kind of request, we decided NOT to change the base class and the base logic of strategy selection. Instead we chose to have a non-invasive way of handling the client request by implementing pricing strategy alternatives through a domain level aspect.
public aspect CalculationStrategySelector {
private List<ICalculationStrategy> strategies;
public void setStrategies(List<ICalculationStrategy> strategies) {
this.strategies = strategies;
}
pointcut inCalculate(PriceCalculation calc)
: execution(* PriceCalculation.calculate(..)) && this(calc);
Object around(PriceCalculation calc)
: inCalculate(calc) {
int i = 0;
int maxRetryCount = strategies.size();
while (true) {
try {
return proceed(calc);
} catch (Exception ex) {
if ( i < maxRetryCount) {
calc.setStrategy(getAlternativeStrategy(i++));
} else {
// handle exceptions
}
}
}
}
private ICalculationStrategy getAlternativeStrategy(int index) {
return strategies.get(index);
}
}
And the options for the selector were configured in the configuration xml of Spring ..
<beans>
<bean id="strategySelector"
class="org.dg.domain.CalculationStrategySelector"
factory-method="aspectOf">
<property name="strategies">
<list>
<ref bean="customStrategy1"/>
<ref bean="customStrategy2"/>
</list>
</property>
</bean>
<bean id="customStrategy1" class="org.dg.domain.CustomCalculationStrategy1"/>
<bean id="customStrategy2" class="org.dg.domain.CustomCalculationStrategy2"/>
</beans>
The custom selectors kicked in only when the default strategy fails. Thanks to AOP, we could handle this problem completely non-invasively without any impact on existing codebase.
And you can hide your complexities too ..
Aspects provide a great vehicle to encapsulate many complexities out of your development team. While going through Brian Goetz's Java Concurrency In Practice, I found one snippet which can be used to test your code for concurrency. My development team has just been promoted to Java 5 features and not all of them are enlightened with the nuances of
java.util.concurrent
. The best way that I could expose the services of this new utility was through an aspect.The following snippet is a class TestHarness and is replicated shamelessly from JCIP ..
package org.dg.domain.concurrent;
import java.util.concurrent.CountDownLatch;
public class TestHarness {
public long timeTasks(int nThreads, final Runnable task)
throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for(int i = 0; i < nThreads; ++i) {
Thread t = new Thread() {
public void run() {
try {
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException ignored) {}
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
My target was to allow my developers to write concurrency test codes as follows ..
public class Task implements Closure {
@Parallel(5) public void execute(Object arg0) {
// logic
// ..
}
}
The annotation
@Parallel(5)
indicates that this method need to be run concurrently in 5 threads. The implementation of the annotation is trivial ..import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Parallel {
int value();
}
The interesting part is the main aspect which implements the processing of the annotation in AspectJ 5 .. Note the join point matching based on annotations and the context exposure to get the number of threads to use for processing.
public aspect Concurrency {
pointcut parallelExecutionJoinPoint(final Parallel par) :
execution(@Parallel public void *.execute(..)) && @annotation(par);
void around(final Parallel par) : parallelExecutionJoinPoint(par) {
try {
long elapsed =
new TestHarness().timeTasks(par.value(),
new Runnable() {
public void run() {
proceed(par);
}
} );
System.out.println("elapsed time = " + elapsed);
} catch (InterruptedException ex) {
// ...
}
}
}
The above example shows how you can build some nifty tools, which your developers will love to use. You can shield them from all complexities of the implementation and provide them the great feature of using annotations from their client code. Under the hoods, of course, it is AspectJ doing all the bigwigs.
In some of the future postings, I will bring out many of my encounters with aspects. I think we are in for an aspect awakening and the very fact that it is being backed by Spring, will make it a double whammy !