← See all articlesJoris PISSONJoris PISSON - Feb 7, 2023

A journey through frontend Aspect-Oriented Programming - Theory

This paper about Aspect-Oriented Programming (AOP) covers common use cases and thoughts around it, some implementation proposals, and techniques that rely on proxies and decorators, with explorations of caveats and alternatives, in a series of articles.

Aspect Oriented Programming applied to Frontend

As a frontend developer, I am often confronted with the same technical needs from one project to another and so on. So I had an idea to improve our technical stack. It might help you design a new frontend architecture. Let's start by looking at existing concepts applicable to frontend development. There is probably room for improvement but I hope that by sharing my ideas with the community, we can make it grow together.

In this series of articles, I share out with you:

  1. What is AOP, common use cases and thoughts around it;
  2. AOP examples based on Proxies (Profiling, logging and memoïzation);
  3. More advanced examples with Decorators (Quota limitation and RBAC).

TLDR;

There is no shortcut to success; Here it covers theories and thoughts about AOP, next week it will be about Proxies and Decorators examples.

AOP is a powerful tool that you should use cautiously. I strongly recommend you read some papers on Proxy, Reflect API, and incoming Decorators. You might find these inspiring.

Okay, okay, here's the link to the repository;There is an overuse of AOP in this TODO App for a didactic purpose and obviously, this App could be improved.

What is AOP

An ancient secret that's been passed down orally?A universal cheat code to unlock your funky self?Have you heard about AOP before?

AOP stands for Aspect-Oriented Programming.

"Cross-cutting concerns" apply both for micro-frontend and backend micro-services with different objectives. It's a way to avoid repetitive stuff in your frontend apps like logging, RBAC, and more.

The beauty here is that you can use AOP with OOP (Object-oriented programing) / FP (Functional programming) without breaking a thing (if it's well done ).

In other words, it will wrap an object and enhance its behavior without modifying its original code.

Once is happenstance. Twice is coincidence. Three times is enemy action.

-Ian Fleming

Benefit of AOP

  1. Separation of Concerns
  2. Modularity
  3. Re-usability
  4. Testability
  5. Maintainability
Russian doll

Common use cases

  • Deprecation: Prevent usage of a class or a function by warning your users before removing it in the next major version.
  • Profiling: Measure how much time your functions take;
  • Logging: Log with context to provide reliable debug traces, and proof of input/output;
  • Obfuscation: Hide sensitive data;
  • Schema validation: Validation of input data content and their functional consistency;
  • Memoïzation: Serve a response you already saved if your backend has recently provided one;
  • Sync Storage: Sync your local state with another storage system;
  • RBAC Limitation: Role-based access control to manage your client authorizations and limitations;
  • Quota limitations: How frequent or how many actions your user can do;
  • Meta-programming : Some of uses are introspection; self-modification; intermediation; intercession…

JavaScript candidacy

Okay, but how to achieve that? Where do I put my stuff in a real App? How to keep the room clean?

Based on the most common language to develop on frontend, JavaScript, what standard feature do we have to enhance an object/class without modifying its original code?

Here are the Proxies

Usage of Proxies statistics from stateofjs.com, 30% heard of it, 43% know about it, 26% have use it, for the year 2021.

A Proxy provides you the ability to intercept and redefine an operation on a target without modifying the original target. A Proxy instance can be used as the original target.

So, this is related to what we are looking for and we've described before: Meta-programming.

Here is an example :

Two towns on cliffs connected by a bridge

Here you have two towns, communicating through a bridge. Let's say the left-hand town is asking for the name of the mayor of the town on the right. A car will be sent out to fetch the information. On his way, the car could be intercepted at a checkpoint.

In a nutshell, the left-hand town represents something trying to interact with a proxied target, which is the town on the right. The bridge serves as an interaction medium between something and a proxied target. The checkpoints are your proxies. And each car represents an interaction with the proxied target.

Alternative to proxies: Decorators

You should take a look at Decorators it's an upcoming feature that might be useful for you in a near future.

Well, [Decorators spec] recently moved to Stage 3 (recent update) proposal and it's an experimental feature for [Typescript spec]. But you can already try it, thanks to babel decorators.

It's just another way to wrap something with a function. You might well have heard of functional composition or higher-order functions.

Proxies vs. Decorators

Proxies could be applied to an instance, whereas Decorators has a prototypal approach (added from a class definition), and each instance will inherit from the augmentation. On behalf of Proxies, this might be overcome with creational patterns.

Proxies are used in combination with traps handler and reflection, which lead to a performance cost. Some trade-offs are expected from the Reflect API as it is a powerful tool. (So it requires disciplines to refrain from overuse)

When you have a hammer in your hands, everything looks like a nail…

-Abraham Maslow

Decorators are also more intuitive to consume because they are consumed only by an element of a class or a class whereas Proxies might need another level of abstraction.

Indeed, Proxies will be helpful when you'll need to add an Aspect externally, without modifying its definition, such as an external library that you don't want to fork.

Moreover, Proxies are also more flexible in combination with Reflect API since they can target a Class, an Object, an Array, or event a Function, they are not limited to a Class like the future Decorators.

Proxy or Decorators is an added layer of indirection and reduces the readability of your code and might require a lot of configuration if used at a large scale.

Decorators can be used both with JavaScript and TypeScript, which require small configuration, while Proxies are a standard feature of JavaScript but it's more tricky with TypeScript because of types inference.

Why not these?

Mixins are a pattern designed to add the same props from a parent node to all its children. Adding content is different from augmenting content. This is an anti-pattern and should be used cautiously. (Mainly due to potential properties collisions)

The same goes for Provide/Inject; I think you should prefer composition over injection in some cases.

Pipelines with Pipe operator is also in Stage 2 JavaScript feature, oriented Functional Programming; Here it does not fit because the pipeline force to use the result of the previous operation into the next.

Some questions you need to ask yourself before proceeding to your Aspect "encapsulations"

  • What? What behaviors are you going to add to enhance a target?
  • Where? Where are these aspects useful? Does it apply to all props or a subset? A ⊂ E (A a subset of E)? If it's a subset, how are you going to apply your Aspect on specific props?
  • Hooks? When should this behavior happen? "Before" accessing a prop/function? "After" returning or throwing?
  • Whoever? Can it be applied to anything (Class, Object, Array, Function)? Do you need a defensive strategy to prevent its usage on a specific type?
  • Generic? Is this Aspect generic and can be applied to multiple use cases, or is it specific to a single-use case?
  • Pure / Transparent? Are these behaviors, that you are adding, going to alter the original behavior of your target? (For Object more specifically, you should check first if it is sealed, extensible, or frozen before going further with this)
  • Coupling / Relationship ? These should be self-contained and do not know other Aspects, nor a specific Object/Class type. Are you creating a dependency? Is it worth it? Although it is not forbidden, you should consider this carefully. In that case, I would recommend writing an ADR (Architecture Decision Record) …
  • Always on? Do you need to enable and disable it? Even "partially"? How are you going to switch this config? With SSE (Server Side Event), Environment Variables, or associated with your Feature flipping config? Is your infrastructure ready for this?
  • Order? Should you add an 'aspect' before or after another? Aspects should not rely on each other, as they must be independent. Regarding the nesting order, it's functional logic that matters.
Question to ask before proceding

Some reflections to help you during this process

Is it worth it to add profiling on top of multiple aspects or is it clever/smarter at the nearest layer of your target? (nested aspect)

When using Memoïzation, and Quota limitation aspects, it's mainly to reduce the impact on your infrastructure and "go green". You should take into account your caching strategy; it might not be a decision to take on your own.

If you need to log your frontend activity for debugging purposes or statistics, you might need to obfuscate some sensitive content like credit card number, social security number, etc …; before throwing them and your different log transporters down the sink.

Like RBAC and Quota limitation; does it modify the original Target behaviors on purpose? When it does, documentation should be related to the Aspect definition. The same goes for the external's "long" running process.

Credit to "borisvigne357e"

Conclusions

AOP is a great tool; You should use it carefully and prevent overuse. In order to do so, read the documentation, know the limitations, ask yourself good questions and involve only the people concerned, write an ADR when you take some big decisions, and then, implement your Aspect.

Next week, we will explore these concepts with some Proxies and Decorators examples.

You should explore this by yourself; like a dungeon crawler; Die and retry, this is a way to forge your knowledge; And then share your experiences with your pairs.