Scala is one those great languages that is scalable. With scalable I mean that it is the language that grows with the user, that it makes simple things easy and hard things possible. A language that is easy to get started and to become productive in, but at the same time a deep language with very powerful constructs and abstractions.
In this blog post I will try to highlight the power of Scala's mixins and how you can use mixin composition to get AOP/interceptor-like style of programming.
First let's define our service interface, modeled as a mixin (in this case without an implementation so similar to Java's interface):
traitStuff{defdoStuff}
Now let's define two different mixin "interceptors" that implement the service interface. The first one manages logging and the other one transaction demarcation (but for simplicity I am just using a dummy mock for TX stuff for now):
As we can see in this example they both override the Stuff.doStuff method. If we look more closely we can see that they follow the same pattern:
Enter method (doStuff)
Do something (log, start tx etc.)
Invoke the same method on super (super.doStuff)
Do something (log, commit tx etc.)
The trick here is in the semantics of the call to super. Here Scala will invoke the next mixin in a stack of mixins, e.g. the same method in the "next" mixin that have been mixed in. Exactly what f.e. AspectJ does in its proceed(..) method and what Spring does in its interceptors.
But before we try this out, let's create a concrete implementation of our Stuff service, called RealStuff:
classRealStuff{defdoStuff=println("doing real stuff")}
Now we have everything we need, so let's fire up the Scala REPL and create a component based on the RealStuff class and a mixin stack with support for logging and transactionality. Scala's mixin composition can take place when we instantiate an instance, e.g. it allows us to mix in functionality into specific instances that object creation time for specific object instances.
First let's create a plain RealStuff instance and run it:
As you can see the call to RealStuff.doStuff is intercepted and logging is added before we are invoking this method as well as after. Let's now add the TransactionalStuff mixin:
As you can see, the semantics for this mixin stack is the exact same as you would get with stacking AspectJ aspects or Spring interceptors. Another interesting aspect is that the whole composition is statically compiled with all its benefits of compile time error detection, performance, potential tool support etc.
This approach is similar to Rickard Oberg's idea on using the so-called Abstract Schema pattern for type-safe AOP in plain Java.
It is both simple and intuitive to change the order of the mixin "interceptors", simply change the order in which they are applied to the target instance:
Finally, just for fun, let's a create a mixin that can retry failing operations. This particular one will catch any exception that the service might throw and retry it three times before giving up:
traitRetryStuffextendsStuff{abstractoverridedefdoStuff{vartimes=0varretry=truewhile(retry){try{super.doStuffretry=false}catch{casee:Exception=>if(times<3){// retry 3 times
times+=1println("operation failed - retrying: "+times)}else{retry=falsethrowe}}}}}
To test this behavior (as well as the rollback feature in the TransactionalStuff) we can change the RealStuff.getStuff method to throw an exception:
classRealStuff{defdoStuff{println("doing real stuff")thrownewRuntimeException("expected")}}
Now we can try to add this mixin to the beginning of our our stack and run the service:
Pretty neat, right?
That's all for now. In the next post I will cover a bunch of ways to use Scala's language primitives to do Dependency Injection (DI).