Ruby and AOP: Decouple your code even more

We, programmers, care for our applications’ design greatly. We can spend
hours arguing about solutions that we dislike and refactoring our code to
loose coupling and weaken dependencies between our objects.

Unfortunately, there are Dark Parts in our apps - persistence,
networking, logging, notifications… these parts are scattered in our code -
we have to specify explicit dependencies between them and domain objects.

Is there anything that can be done about it or is the real world a nightmare for purists?
Fortunately, a solution exists.
Ladies and gentlemen, we present aspect-oriented programming!

A bit of theory

Before we dive into the fascinating world of AOP, we need to grasp some concepts
which are crucial to this paradigm.

When we look at our app we can split it into two parts: aspects
and components. Basically, components are parts we can easily
encapsulate into some kind of code abstraction - a methods, objects or procedures.
The application’s logic is a great example of a component.
Aspects, on the other hand, can’t be simply isolated in code - they’re things
like our Dark Parts or even more abstract concepts - such as ‘coupling’ or 'efficiency’.
Aspects cross-cut our application - when we use some kind of persistence (e.g. a database) or network communication (such as ZMQ sockets)
our components need to know about it.

Aspect-oriented programming aims to get rid of cross-cuts by separating
aspect code from component code using injections of our aspects in certain join points
in our component code. The idea comes from Java community and it may sound a bit scary at first
but before you start hating -
read an example and everything should get clearer.

Let’s start it simple

Imagine: You build an application which stores code snippets. You can start one of the usecases this way:

Here we have a simple usecase of inserting snippets to the application.
To perform some kind of SRP check, we can ask ourselves: What’s the responsibility of this object? The answer can be: It’s responsible for pushing snippets scenario. So it’s a good, SRP-conformant object.

However, the context of this class is broad and we have dependencies - very weak, but still dependencies:

Repository object which provides persistence to our snippets.

Logger which helps us track activity.

Use case is a kind of a class which belongs to our logic. But it knows about aspects in our app - and we have to get rid of it to ease our pain!

Introducing advice

I have told you about join points. It’s a simple, yet abstract idea - and how can we turn it into something specific? What are the join points in Ruby?
A good example of join point (used in the aquarium gem) is an invocation of method. We specify how we inject our aspect code using advice.

What are advice? When we encounter a certain join point, we can connect it with an advice, which can be one of the following:

Evaluate code after given join-point.

Evaluate code before given join-point.

Evaluate code around given join-point.

While after and before advice are rather straightforward, around advice is cryptic - what does it mean to “evaluate code around” something?

In our case it means: Don’t run this method. Take it and push to my advice as an argument and evaluate this advice. In most cases after and before advice are sufficient.

Notice the empty method user_pushed- it’s perfectly fine, we’re maintaining it only to provide a join point for our solution. You’ll often see empty methods in code written in AOP paradigm. In my code, with a bit of metaprogramming, I turn it into a helper, so it becomes something like:

join_point:user_pushed

Now we can test this unit class without any stubbing or mocking. Extremely convenient, isn’t it?

Afterwards, we have to provide aspect code to link with our use case. So, we create SnippetsUseCaseGlue class:

And that’s it. Now our use case is a pure domain object, without even knowing it’s connected with some kind of persistence and logging layer. We’ve eliminated aspects knowledge from this object.

Further read:

Of course, it’s a very basic use case of aspect oriented programming. You can be interested in expanding your knowledge about it and these are my suggestions:

Ports and adapters (hexagonal) design - one of the most useful usecases of using AOP to structure your code wisely. Use of AOP here is not needed, but it’s very convenient and in Arkency we favor to glue things up with advice instead of evented model, where we push and receive events.

Summary

Aspect-oriented programming is fixing the problem with polluting pure logic objects with technical context of our applications. Its usecases are far broader - one of the most fascinating usecase of AOP with a huge 'wow factor’ is linked in the 'Further Read’ section. Be sure to check it out!

We’re using AOP to separate these aspects in chillout - and we’re very happy about it. What’s more, when developing single-page apps in Arkency we embrace AOP when designing in hexagonal architecture. It performing very nice - just try it and your application design will improve.

Someone can argue:

It’s not an improvement at all. You pushed the knowledge about logger and persistence to another object. I can achieve it without AOP!

Sure you can. It’s a very simple usecase of AOP. But we treat our glues as a configuration part, not the logic part of our apps. The next refactor I would do in this code is to abstract persistence and logging objects in some kind of adapter thing - making our code a bit more 'hexagonal’ ;). Glues should not contain any logic at all.

I’m very interested in your thoughts on AOP. Have you done any projects embracing AOP? What were your use cases? Do you think it’s a good idea at all?