AOP vs functions

Roman Elizarov
3 min readJan 6, 2019

Aspect-oriented programming (AOP) is quite popular. Motivation for it is well explained in the corresponding Wikipedia article.

AOP is a great tool for truly global concepts like logging that do not directly affect the logic of the code. However, problems with AOP show up when it is used for more business-related things like authorization. These aspects of the application have to be clearly visible in the relevant code, so that a developer can immediately see if they are implemented properly when reading the corresponding source code. AOP-based frameworks typically solve for it by using method annotations:

@RequireRole(Role.Admin) // clearly visible aspect
fun updateUserPermissions(…) {
// logic here
}

However, from the readability standpoint it is not much different from a functional approach to the same problem using requireRole function instead of @RequireRole annotation:

fun updateUserPermissions(…) {
requireRole(Role.Admin) // throws SecurityException
// logic here
}

Moreover, the functional approach has the advantage that it also scales up to more complex permissions checks, like analyzing method parameters before deciding which user role is required.

The same is true for other aspects like transactions. Unfortunately, it is cumbersome and inconvenient to functionally represent more complex concepts in Java, which creates an artificial popularity for AOP frameworks in Java ecosystem.

It is not the case with Kotlin, though. In Kotlin, instead of Java-like approach to transactions with AOP and annotations like this:

@Transactional
fun updateUserPermissions(…) {
// logic here
}

It is just as readable and clean-looking when rewritten in a functional way:

fun updateUserPermissions(…) = transactional {
// logic here
}

The advantage of this functional approach is that you can always Ctrl/Cmd+Click on the declaration of transactional function in your IDE and immediately see what exactly it does, which is not usually possible with any of the commonly used AOP frameworks. Even when navigation to the aspect source code is provided by an IDE plugin, deciphering its logic requires knowledge of a separate rich API and/or conventions.

Unfortunately, this functional replacement for annotation-based AOP in Kotlin does not immediately scale to the case when multiple aspects are applied to the same function as curly braces and indentation start to pile up:

fun updateUserPermissions(…) = logged {
transactional {
// logic here
}
}

The work-around is to create a combined higher-order function to keep the use-site of multiple aspects clean-looking:

fun updateUserPermissions(…) = loggedTransactional {
// logic here
}

Another disadvantage of functional approach is that aspects like logging need access to method parameters. They are usually directly available in traditional AOP frameworks via special APIs, but the stock Kotlin functions cannot readily access them. So, in order to actually represent real-life logging aspect in a purely functional way, one still has to write a considerable amount of boiler-plate code:

fun updateUserPermissions(params: Params) = 
logged("updateUserPermissions($params)") {
// logic here
}

This consideration still makes AOP a tool of choice for logging when you really need to have it globally and consistently in your application, but I believe that using AOP for aspects like authorization and transactions is an abuse, given rich functional abstractions that are available in Kotlin. Functions do handle these aspects better and cleaner.

To conclude I’d say that further improvements in functional abstractions to provide an even better replacement for AOP could be a promising vector of future evolution for Kotlin language. Java-based AOP frameworks are usually JVM-specific and are perceived as some opaque magic, while Kotlin functional abstractions are really cross-platform and are transparent to the user.

--

--

Roman Elizarov

Project Lead for the Kotlin Programming Language @JetBrains