DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Thermometer Continuation in Scala
  • Deploying a Scala Play Application to Heroku: A Step-by-Step Guide
  • Upgrading Spark Pipelines Code: A Comprehensive Guide
  • Java vs. Scala: Comparative Analysis for Backend Development in Fintech

Trending

  • Scaling InfluxDB for High-Volume Reporting With Continuous Queries (CQs)
  • Streamlining Event Data in Event-Driven Ansible
  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • Emerging Data Architectures: The Future of Data Management
  1. DZone
  2. Coding
  3. Languages
  4. Real-World Scala: Managing Cross-Cutting Concerns using Mixin Composition and AOP

Real-World Scala: Managing Cross-Cutting Concerns using Mixin Composition and AOP

By 
Jonas Bonér user avatar
Jonas Bonér
·
Dec. 10, 08 · Interview
Likes (0)
Comment
Save
Tweet
Share
19.6K Views

Join the DZone community and get the full member experience.

Join For Free

In a previous post I showed you how you could use mixin composition and self type annotations to enable Dependency Injection (DI). Mixin composition is an extremely powerful tool that you can utilize in many different ways to enable modular and reusable code. In this post I’ll try to show you how you can use it to solve the problem of crosscutting concerns using AOP/interceptor-style composition.

Crosscutting concerns and AOP

OOP has given us tools to reduce software complexity by introducing concepts like inheritance, abstraction, and polymorphism. However, developers face daily problems in software design that can’t be solved easily using OOP. One of these problems is how to handle cross-cutting concerns in the application.

So what is a cross-cutting concern? A concern is a particular concept or area of interest. For example, in an ordering system the core concerns could be order processing and manufacturing, while the system concerns could be transaction handling and security management. A cross-cutting concern is a concern that affects several classes or modules, a concern that is not well localized and modularized.

Symptoms of a cross-cutting concern are:

  • Code tangling - when a module or code section is managing several concerns simultaneously
  • Code scattering - when a concern is spread over many modules and is not well localized and modularized

These symptoms affect software in various ways; for example, they make it harder to maintain and reuse software as well as harder to write and understand.

Aspect-Oriented Programming (AOP) tries to solve these problems by introducing the concept of separation of concerns, in which concerns can be implemented in a modular and well-localized way. AOP solves this by adding an extra dimension to the design space, and introduces constructs that allow us to define the cross-cutting concerns, to lift them out into a new dimension and package them in a modular way.

We are currently using two different types of interceptors (aspects if you like):

  • Mixin composition stacks — a limited but sometimes very useful approach
  • Generic interceptors/aspects using a pointcut pattern language

Mixin composition stacks

Mixin composition stacks is a core language feature of Scala and is similar to Rickard Oberg’s idea on using the so-called Abstract Schema pattern for type-safe AOP in plain Java. (This is a very contrived example that probably shows that don’t know a zip about dogs, but please bare with me.)

First let’s define a couple of interfaces; Dog and DogMood modeled as a mixins (in this case without an implementation so similar to Java’s interface):

trait Dog {
def greet(me: Human)
}

trait DogMood extends Dog {
def greet(me: Human) {
println(me.sayHello)
}
}

Now let’s define two different mixin “interceptors” that implement these interfaces. The first one defining an angry dog and the other one a hungry dog:

trait AngryDog extends DogMood {
abstract override def greet(me: Human) {
println("Dog: Barks @ " + me)
super.doStuff(me)
}
}

trait HungryDog extends DogMood {
abstract override def greet(me: Human) {
super.doStuff(me)
println("Dog: Bites " + me)
}
}

As we can see in this example they both override the Mood.greet method. If we look more closely we can see that they follow the same pattern:

  • Enter method (greet)
  • Do something
  • Invoke the same method on super (super.greet)
  • Do something else

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.

Now let’s fire up the Scala REPL and create a component based on the Dog interface. 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.

scala> val dog = new Dog with AngryDog with HungryDog
stuff2: Dog with AngryDog with HungryDog = $anon$1@1082d45

scala> dog.greet(new Human("Me"))
Dog: Barks @ Me
Me: Hello doggiedoggie
Dog: Bites Me

As you can see the call to Dog.greet is intercepted by the different moods that are added to the dog at instantiation time.

Interceptors like this are as you can see not generically reusable since they are tied to a specific interface, however if well designed can be a pretty powerful technique. It has the advantage that everything is statically compiled and type-checked by the Scala compiler

Generic pointcut-based aspects

The main usage of generic aspects is for implementing infrastructure concerns such as logging, transaction demarcation, security, clustering, persistence etc.

In order to create a framework for implementing generic aspects, the first thing we need to do is to define an invocation context, holding arguments, method to be invoked as well as the target instance.

case class Invocation(val method: Method, val args: Array[AnyRef], val target: AnyRef) {
def invoke: AnyRef = method.invoke(target, args:_*)
override def toString: String = "Invocation [method: " + method.getName + ", args: " + args + ", target: " + target + "]"
override def hashCode(): Int = { ... }
override def equals(that: Any): Boolean = { ... }
}

The second thing that we need to do is to create a base Interceptor trait. This interface defines two different pointcut matching methods. The first one matches a precompiled AspectJ pointcut expression using the PointcutParser in AspectJ. This allows defining interceptors matches AspectJ compatible (method) pointcut expressions. The second matcher matches methods or classes that is annotated with a specific annotation.

trait Interceptor {
protected val parser = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution

protected def matches(pointcut: PointcutExpression, invocation: Invocation): Boolean = {
pointcut.matchesMethodExecution(invocation.method).alwaysMatches ||
invocation.target.getClass.getDeclaredMethods.exists(pointcut.matchesMethodExecution(_).alwaysMatches) ||
false
}

protected def matches(annotationClass: Class[T] forSome {type T < : Annotation}, invocation: Invocation): Boolean = {
invocation.method.isAnnotationPresent(annotationClass) ||
invocation.target.getClass.isAnnotationPresent(annotationClass) ||
false
}

def invoke(invocation: Invocation): AnyRef
}

The last thing we need to do is to create a factory method allows us to wire in our interceptors, declarative, in a seamless fashion. This factory is using the plain old Java Dynamic Proxy API to create a proxy for our base components.

object ManagedComponentFactory {
def createComponent[T](intf: Class[T] forSome {type T}, proxy: ManagedComponentProxy): T =
Proxy.newProxyInstance(
proxy.target.getClass.getClassLoader,
Array(intf),
proxy).asInstanceOf[T]
}

class ManagedComponentProxy(val target: AnyRef) extends InvocationHandler {
def invoke(proxy: AnyRef, m: Method, args: Array[AnyRef]): AnyRef = invoke(Invocation(m, args, target))
def invoke(invocation: Invocation): AnyRef = invocation.invoke
}

Just using this factory pass is won’t do any wiring for us, which is actually good since if we would use the dynamic proxy the old-fashioned way and we would have it to invoke each interceptor explicitly using reflection. But we can do better than that. Instead we will let the Scala compiler statically compiled in an interceptor stack with all our interceptors. This is best explained with an example.

In this example we will define a couple of simple services called Foo and Bar along with their implementations. We will then implement two different infrastructure in interceptors; logging and transaction demarcation.

Let’s first define the service.

  import javax.ejb.{TransactionAttribute, TransactionAttributeType}

trait Foo {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
def foo(msg: String)
def bar(msg: String)
}

class FooImpl extends Foo {
val bar: Bar = new BarImpl
def foo(msg: String) = println("msg: " + msg)
def bar(msg: String) = bar.bar(msg)
}

trait Bar {
def bar(msg: String)
}

class BarImpl extends Bar {
def bar(msg: String) = println("msg: " + msg)
}

Now let’s define a logging interceptor. Both of these interceptors are just mockups, since the actual implementation is not really of interest. The logging interceptor is defined using a standard AspectJ pointcut while the transaction interceptor is wired to a specific annotation.

  trait LoggingInterceptor extends Interceptor {
val loggingPointcut = parser.parsePointcutExpression("execution(* *.foo(..))")

abstract override def invoke(invocation: Invocation): AnyRef =
if (matches(loggingPointcut , invocation)) {
println("=====> Enter: " + invocation.method.getName + " @ " + invocation.target.getClass.getName)
val result = super.invoke(invocation)
println("=====> Exit: " + invocation.method.getName + " @ " + invocation.target.getClass.getName)
result
} else super.invoke(invocation)
}

trait TransactionInterceptor extends Interceptor {
val matchingJtaAnnotation = classOf[javax.ejb.TransactionAttribute]

abstract override def invoke(invocation: Invocation): AnyRef =
if (matches(matchingJtaAnnotation, invocation)) {
println("=====> TX begin")
try {
val result = super.doStuff
println("=====> TX commit")
result
} catch {
case e: Exception =>
println("=====> TX rollback ")
}
} else super.invoke(invocation)
}

Now let’s do the wiring. Here we are using dynamic proxy-based factory that we implemented because you can see, the actual wiring on the interceptor stack is done using Scala mixing composition and therefore has all its benefits, like compiler type checking and enforcement, the speed of statically compiled code, refactoring safety etc.

  var foo = ManagedComponentFactory.createComponent[Foo](
classOf[Foo],
new ManagedComponentProxy(new FooImpl)
with LoggingInterceptor
with TransactionInterceptor)

foo.foo("foo")
foo.bar("bar")
}

This will produce the following output:

=====> TX begin
=====> Enter: foo @ FooImpl
msg: foo
=====> Exit: foo @ FooImpl
=====> TX commit
=====> TX begin
=====> Enter: bar @ FooImpl
msg: bar
=====> Exit: bar @ FooImpl
=====> TX commit

So this wraps it up. I hope that you have learned a little bit about how powerful mixin composition in Scala is and how it can be used to write modular and reusable components with little effort.

This is working fine for us, but there is definitely room for improvement. For example, runtime matcher in the interceptor is fast enough for the annotation matching (only a boolean check) but the AspectJ pointcut matcher is a bit slower since it has to do some more work. This might turn out be a problem or not, most infrastructure services (like persistence and security) performs quite a lot to work and in these cases the overhead over the interceptor of matching will not affect the overall performance much, but in other cases (such as logging or auditing) it might. We are so far only use the annotation matching, so it has not turned out to be a problem so far. However, if it turns out to be a performance bottleneck then we will most likely switch to using my old AspectWerkz AWProxy to get rid of all the Java reflection code and runtime matching.

For those that are interested, here is the actual JTA transaction demarcation interceptor that we are using in production (implementing all the EJB transaction semantics).

trait EjbTransactionInterceptor extends Interceptor with TransactionProtocol {
val matchingJtaAnnotation = classOf[javax.ejb.TransactionAttribute]

abstract override def invoke(invocation: Invocation): AnyRef = if (matches(matchingJtaAnnotation, invocation)) {
val txType = getTransactionAttributeTypeFor(invocation.target.getClass, invocation.method)
if (txType == TransactionAttributeType.REQUIRED) withTxRequired { super.invoke(invocation) }
else if (txType == TransactionAttributeType.REQUIRES_NEW) withTxRequiresNew { super.invoke(invocation) }
else if (txType == TransactionAttributeType.MANDATORY) withTxMandatory { super.invoke(invocation) }
else if (txType == TransactionAttributeType.NEVER) withTxNever { super.invoke(invocation) }
else if (txType == TransactionAttributeType.SUPPORTS) withTxSupports { super.invoke(invocation) }
else if (txType == TransactionAttributeType.NOT_SUPPORTED) withTxNotSupported { super.invoke(invocation) }
else super.invoke(invocation)
} else super.invoke(invocation)
}

About this entry

You’re currently reading “Real-World Scala: Managing Cross-Cutting Concerns using Mixin Composition and AOP,” an entry on Jonas Bonér

Published:
12.09.08 / 5am
Category:
AOP, Scala
Scala (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Thermometer Continuation in Scala
  • Deploying a Scala Play Application to Heroku: A Step-by-Step Guide
  • Upgrading Spark Pipelines Code: A Comprehensive Guide
  • Java vs. Scala: Comparative Analysis for Backend Development in Fintech

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!