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):
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.)
Stuff
service, called RealStuff
:
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:
Not too exciting, but let's do it again and this time mix in the LoggableStuff
mixin:
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:
To test this behavior (as well as the rollback feature in the TransactionalStuff
) we can change the RealStuff.getStuff
method to throw an exception:
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).