Implementing Onion architecture in NestJS

What’s Onion architecture?

As shown in the picture, onion architecture is a way to structure the code by dividing it into domain-driven design layers. each layer can only access the layer below it throw its interfaces, and then using the dep…


This content originally appeared on DEV Community and was authored by Amro

What’s Onion architecture?

As shown in the picture, onion architecture is a way to structure the code by dividing it into domain-driven design layers. each layer can only access the layer below it throw its interfaces, and then using the dependency inversion principle each interface will be replaced with its class.

onion architecture

Onion Architecture leads to more maintainable applications since it emphasizes separation of concerns throughout the system.” Jeffery Palermo

Why apply Onion Architecture in NestJs projects?

NestJs is a service-side framework based on NodeJs. NestJs has many built-in features but most importantly for us now is the Dependency Injection and the possibility to add Dependency inversion which is what we need to apply the Onion Architecture.

Building simple server-side Blog:

In the rest of the article, I’ll try to explain the NestJs implementation using a simple blog project.

I’ll assume most of the article readers already know Nestjs so I’ll focus on the architecture in the code example.

Project layers in NestJs

project layers in nestjs

  • Domain entities: in the core of our application we have the domain, according to the Domain-driven design(DDD), we should focus our implementation around our Domain and all other layers are built around it.

In our case, the Domain Entity is just the Article, so let’s do its interface:

export interface IArticle {
  id: number;
  title: "string;"
  body: string;
}
  • Repository: is the layer that participates in the Domain Entity, like getting or deleting entity object but it has to abstract away database and infrastructure details, so it’ll work with any kind of database.

so let’s build ArticleRepository:

export interface IArticleRepository {
  get(id: number): Promise<IArticle>;
  delete(id: number): Promise<void>;
  save(input: IArticle): Promise<void>;
  update(input: IArticle): Promise<IArticle>;
}

As the Repository interface most likely will have the same functions for all Entities as it should mainly perform these abstract functions, I recommend using Generics that takes the Entity type as parameter to have general Repository

export interface IRepository<T> {
  get(id: number): Promise<T>;
  delete(id: number): Promise<void>;
  save(input: T): Promise<void>;
  update(input: T): Promise<T>;
}
  • Service: here we implement the use cases, it participates in the Repository to get the data it needs

in our case we need a service to get an article by id and count article characters:

export interface IArticleService {
  getArticle(id: number): Promise<IArticle>;
  getArticleLength(id: number): Promise<number>;
}
  • Controller: for the sake of simplification I made the controller the first layer, hence NestJs is a server-side framework. But normally in literature first layer suppose to be the UI or test.

As Controller is our first layer and we won’t use it as dependencies somewhere else so no need to write an interface for it and we can implement it directly.

import { Controller, Get, Param } from '@nestjs/common';
import { IArticle } from './article.interface';
import { IArticleService } from './articleService.interface';

@Controller({ path: 'article' })
export class ArticleController {
  constructor(private readonly service: IArticleService) {}

  @Get(':id')
  async article(@Param() params): Promise<IArticle> {
    return this.service.getArticle(params.id);
  }
}

So are we done?

ofc not yet, we just wrote the interfaces but still didn’t write the actual implementation of them, and then replace the interface with the class in the run time.
I’ll implement one of the Classes(ArticleService) and the rest will be the same.

Implementing ArticleService


@Injectable()
export class ArticleService implements IArticleService {
  constructor(
private readonly repository: IRepository<IArticle>) {}

  getArticleLength(id: number) {
    return this.repository.get(id).then(
(article) => article.body.length);
  }

  getArticle(id: number) {
    return this.repository.get(id);
  }
}
  • must add @Injectable() so NestJs can inject it later in a class as dependency.
  • the class implements the interface to make sure the class has the same functions
  • as we see here ArticleService has dependencies like the Controller but this time its dependency is IArticleRepository

Dependency inversion( replacing the Interface by the Class):

In the ArticleModule we can specify a string token for each class and use this token when we use the class as a dependency.

so let’s apply that with ArticleService, giving it a token in ArticleModule:

@Module({
  controllers: [ArticleController],
  providers: [
    {
      provide: 'ARTICLE_SERVICE_TOKEN',
      useClass: ArticleService,
    },
  ],
})
export class ArticleModule {}

and using this token in the Controller to get the Class in run it:


@Controller({ path: 'article' })
export class ArticleController {
  constructor(
    @Inject('ARTICLE_SERVICE_TOKEN')
    private readonly service: IArticleService,
  ) {}
  ...
}

Note: as strings are bound to errors, it’s best practice to assign the token string to a const variable and export it from the IArticleService file and then use it instead of the string directly:

export const ARTICLE_SERVICE_TOKEN = 'ARTICLE_SERVICE_TOKEN';

export interface IArticleService {
  getArticle(id: number): Promise<IArticle>;
  getArticleLength(id: number): Promise<number>;
}

— that’s it 🎉 now imagine if we needed to change any class, we’ll just add the new class to the useClass parameter in the Module without having to change the controller implementation.
depenciey inversion spongbob

Don’t be so radical about it:

In the end, Onion Archecturie was made to make the development process easier, so don’t try to force it everywhere where it does not make much sense due to some libraries limitation or other reasons.

Thanks for writing till the end and wish we meet in another article. take care!


This content originally appeared on DEV Community and was authored by Amro


Print Share Comment Cite Upload Translate Updates
APA

Amro | Sciencx (2021-11-18T10:52:44+00:00) Implementing Onion architecture in NestJS. Retrieved from https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/

MLA
" » Implementing Onion architecture in NestJS." Amro | Sciencx - Thursday November 18, 2021, https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/
HARVARD
Amro | Sciencx Thursday November 18, 2021 » Implementing Onion architecture in NestJS., viewed ,<https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/>
VANCOUVER
Amro | Sciencx - » Implementing Onion architecture in NestJS. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/
CHICAGO
" » Implementing Onion architecture in NestJS." Amro | Sciencx - Accessed . https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/
IEEE
" » Implementing Onion architecture in NestJS." Amro | Sciencx [Online]. Available: https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/. [Accessed: ]
rf:citation
» Implementing Onion architecture in NestJS | Amro | Sciencx | https://www.scien.cx/2021/11/18/implementing-onion-architecture-in-nestjs/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.