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.
@Min() Can be used to add constraint on property
age that validates the value being set.
public age: number;
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 and
SETNX 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
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
- Call the
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.
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.
Line 12: const LockServiceinjector = Inject(LockService);
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);
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 :)