A journey through frontend Aspect-Oriented Programming - Decorators
This paper about Aspect-Oriented Programming (AOP) covers common use cases and thoughts around it. Last week we have covered some theories and thoughts about AOP, and then Proxies. Today we will focus on Decorators.
TLDR;
AOP is a powerful tool that you should use cautiously. I strongly recommend you read some papers on Proxy, Reflect API, and incoming Decorators. Decorators are super handy to use but limited to a Class definition. Today’s examples will be more advanced (Quota limitation and RBAC).
Here is the link to the demo app repository; There is an overuse of AOP for a didactic purpose and obviously, this App could be improved.
Some reminders around Decorators
[Decorators spec] recently moved to Stage 3 proposal and it’s an experimental feature for [Typescript spec].
Decorators are functions relating to classes or class elements (fields, methods, accessors).
- They can replace the value that is being decorated with a matching value that has the same semantics. (e.g. a decorator can replace a method with another method, a field with another field, a class with another class, and so on).
- They can provide access to the value that is being decorated via accessor functions which they can then choose to share.
- They can initialize the value that is being decorated, running additional code after the value has been fully defined. In cases where the value is a member of a class, then initialization occurs once per instance.
- Decorators are called (as functions) during class definition, after the methods have been evaluated but before the constructor and prototype have been put together.
- Evaluation goes left to right, top to bottom.
Quota limitation example
What is a “quota limitation”?
Reminder, Quota limitation allow a user to trigger an action a limited amount of times for a period (or permanently 🤔 ?). There might be concurrent limitations like:
- 50 times per minute
- 500 times per hour
- 2000 times per day
Why do we have some “quota limitations”?
Mainly to prevent overuse of our backend API but also to handle a Freemium/Premium “aspect” of our application or computational complexity, when we want to limit user actions (e.g: limit the number of items selected to compute an NP-complete problem)
In my own opinion, quota limitations should be associated with a feature;
Or even better, when possible, an action of a Feature.
So, how do we process to do a such thing?
For a feature action, you’ll get a list of quotas like this :
If you are interested in achieving quota limitations more than AOP itself, take a look at the QuotaService; I have used inversion of control with a generator to sync the hits (it hold the state), and wrapped it in a singleton class instance. It’s also coupled with the FeatureService that consumes the JSON feature config file like the previous one.
Previously we talk about Proxies, but today is about Decorators, so we will use this:
The decorator is wrapped within a function so it can make it reusable and configurable (featureName). A guard clause will prevent its usage to something else than a Class method (context.kind). And then, execution of the “enhance” method of a class will be called if the generator result allows it.
RBAC Limitation example
What’s RBAC?
Role-based access control (RBAC) refers to the idea of assigning permissions to users based on their role within an organization. When using RBAC, you analyze the needs of your users and group them into roles based on common responsibilities. You then assign one or more roles to each user and one or more permissions to each role.
N.B: You still need to secure your API calls with at least a token (OAuth 2.0, OpenID, SAML, …)
As a Frontend developer, you have 2 different considerations in mind related to RBAC :
- Functional: Check if your user is allowed to do something before triggering a Frontend time-consuming computation or an API call (Go green !);
- Presentational: How do you bind perms to your presentation layer (components)? To disable or hide functionalities depending on your business orientation.
We are leaving Marketing and UX considerations to their respective teams, but note that if someone asks you to implement some variants of a component that consumes/displays the same data and has the same functionalities, there is something wrong …
Product Owner (P.O) in concertation with marketing will say :
- If the User has perms ‘Zeta’ :
- “This” should be visible and enabled
- If the User does not have perms ‘Zeta’ :
- “This” should be visible but disabled
- “This” should be hidden
Also, keep in mind that your component should not hold a business logic directly, nor fetch data; it belongs to a service class or for your state manager to do it directly on the frontend, otherwise, it can be achieved during ‘static’ / ‘hybrid static’ (also known as incremental regeneration) / ‘server-side’ generation if you are using tech such as Next / Nuxt.
By default, this Aspect should say “No!”; then it thinks about giving you what you want, just like a real Cyber Security Engineer. So, here is a proposal of implementation also related to features.
Features describe which perms they are associated with, we should have permissions named like this featureName.permission :
And assuming you have an endpoint to retrieve user roles / perms such as :
Same as quota limitation, if you are interested in RBAC more than AOP, take a look at the RbacService, but note that perms should be exposed as a frozen object to avoid security issues. Now, your UI Components can declare reactive props based on the RbacService instance using rbacService.hasPerms(perm) to hide/disable some content.
But when it comes to actions, let’s say, a user wants to “Add” a task to a TO DO list. You can add your RBAC Aspect around a class method .Business logic lies into a service. It describes actions a user could do (Add / Remove).
Again, here you’ve seen that it is easier to use Decorators when you are implementing your own Classes. Otherwise, it will have been way more tricky.
But when it’s a black box library, Proxies come to the rescue.
Conclusions
AOP is a great tool; You should use it carefully and prevent overuse.
Decorators are a beautiful way to do metaprogramming; To enhance your code without adding complexity to your business logic; It's straightforward, top to bottom, right to left, but Decorators are limited to classes. If you need to do kind of the same thing with Objects, Arrays, or even Functions, you should take a look at Proxies.
Remember, 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.