NestJS: Clean Code With Decorators
I mainly use decorators to keep my code clean, readable in a more logical and expressive way. To be honest I am heavily inspired by annotation usage of Java Spring Boot. In this article, I will be concentrating on one technique where I have used decorators to clean my code.
What is a Decorator?
TL;DR Decorator is a sort of declaration that can be used changing behaviour of a property or adding meta data to a class/property/method or extending functionality of a method etc.
In short, a function applied with prefix @ that gets automatically executed at runtime.
Example: @Min()
Can be used to add constraint on property age
that validates the value being set.
@Min(18)
public age: number;
Premise
In one of our recent projects, I had to apply concurrency lock on an existing method. Distributed shared lock is pretty easy and achievable in many ways. I chose Redis andSETNX
command for this purpose.
See more on this: Distributed Shared Lock With Redis
Note: Distributed shared lock with Redis is not the scope of this article.
Let’s assume we have a LockService
that exposes two methods acquireLock(key)
and releaseLock(key)
See: Service class code
Now, three common steps whenever to apply concurrency lock on any existing method as follows
- Call the
acquireLock()
at the very beginning of the method. - Wrap the entire existing method code in
try/catch
- Call the
releaseLock()
infinally
block
Disadvantage Of Above Code Style
The above code looks pretty messy and cumbersome every time any existing method needed to be wrapped with concurrency lock.
End Goal
The goal is to get rid of the above three common steps required every time we need locking.
Let’s look at the custom decorator @ConcurrencyLock() that will be used in place to wrap the existing function and will clean the above messy code.
Implementation Of @ConcurrencyLock() decorator
We can achieve the above goal easily with Method Decorator. Let’s have a look into the code.
Explanation
I am not going to explain line by line. I believe, little javascript/typescript learning would suffice to understand the code easily.
Line 12: const LockServiceinjector = Inject(LockService);
NestJS has Inject
API that can be used to get LockService
injector ( Not documented anywhere ). Got from stackoverflow :)
Line 19: LockServiceinjector(target, 'lockService');
This is same as injecting service via constructor.
Line 22: descriptor.value = async function wrapper(...args: any[])...
It’s the wrapper function that overrides the existing function and invokes the original method internally return await method.apply(this, args);
Conclusion
Don’t overlook decorators. They are cool building blocks to work with. Don’t confine yourself using the only decorators provided by the framework out of the box, better off, code your own decorators.
Good Luck :)