Coder Social home page Coder Social logo

clean-architecture-nestjs's Introduction

Clean Architecture With NestJS

Description

It's been a while since my last article on how to implement clean architecture on Node.js applications, git repo. After working with NestJS and TypeScript I thought it was a good idea to come back and write a new article on the subject. This time we are going to take the super power of typescript and the methodologies and tools of NestJS and harness them to our benefits.

Coming from a background of object-oriented languages, it was natural that we wanted to keep all our SOLID principles in our new and shiny node API.

Like any other architecture, we had to make different trade-offs in the implementation.

We had to be careful not to over-engineer or over-abstract our layers, but rather keep it as flexible as needed.

In recent years, we have implemented clean architecture by Robert C. Martin (Uncle Bob) on our API projects. This architecture attempts to integrate some of the leading modern architecture like Hexagonal Architecture, Onion Architecture, Screaming Architecture into one main architecture. It aims to achieve good separation of concerns. Like most architecture, it also aims to make the application more flexible to inevitable changes in client requirements (which always happens).

clean architecture diagram - dependencies direction are from outside in. source

This diagram is taken from the official article by Robert C. Martin. I recommend reading his article before diving into the node implementation. This is the best source knowledge about this architecture.

Few words about this diagram and how to read it:

  • Dependency - the dependency direction is from the outside in. meaning that the Entities layer is independent and the Frameworks layer depend on all the other layers.

  • Entities - contains all the business entities that construct our application.

  • Use Cases - This is where we centralize our logic. Each use case orchestrates all of the logic for a specific business use case.

  • Controllers and Presenters - Our controller, presenters, and gateways are intermediate layers. You can think of them as an entry and exit gates to the use cases .

  • Frameworks - This layer has all the specific implementations. The database, the web frameworks, error handling etc.
    Robert C. Martin describes this layer :
    “This layer is where all the details go. The Web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.”

In this point you will probably say to yourself “database is in outer layer, database is a detail ???” database is supposed to be my core layer.

I love this architecture because it has a smart motivation behind it. Instead of focusing on frameworks and tools, it focuses on the business logic of the application. This architecture is framework independent (or as much as it can be). This means it doesn’t matter which database, frameworks, UI, external services you are using, the entities and the business logic of the application will always stay the same. We can change all of the above without changing our logic. This is what makes it so easy to test applications built on this architecture. Don’t worry if you don’t understand this yet, we will explore it step-by-step.

Getting Started

Dependencies

  • mongoDb - you need to provide a valid mongDb connection string. add a new environment variable named CLEAN_NEST_MONGO_CONNECTION_STRING
export CLEAN_NEST_MONGO_CONNECTION_STRING='valid mongoDB connection string' 

Installing

npm install

Executing program

npm start

Authors

Royi Benita

Version History

  • 1.0

License

This project is licensed under the [NAME HERE] License - see the LICENSE.md file for details

Acknowledgments

Inspiration, code snippets, etc.

clean-architecture-nestjs's People

Contributors

royib avatar royibenita avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

clean-architecture-nestjs's Issues

Type 'UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>' is not assignable to type 'T'.

Hi again,

for some reason I got the following error that comes from the MongoGenericRepository.

Here's the mongo generic repo:

import { Model } from 'mongoose';
import { IGenericRepository } from 'src/core/abstracts/generic-repository.abstract';

export class MongoGenericRepository<T> implements IGenericRepository<T> {
  private _repository: Model<T>;
  private _populateOnFind: string[];

  constructor(repository: Model<T>, populateOnFind: string[] = []) {
    this._repository = repository;
    this._populateOnFind = populateOnFind;
  }

  getAll(): Promise<T[]> {
    return this._repository.find().populate(this._populateOnFind).exec();
  }

  get(id: any): Promise<T> {
    return this._repository.findById(id).populate(this._populateOnFind).exec();
  }

  create(item: T): Promise<T> {
    return this._repository.create(item);
  }

  update(id: string, item: T) {
    return this._repository.findByIdAndUpdate(id, item);
  }
}

The error is in the get method, here's the whole traceback:

src/infrastructure/data-services/mongo/mongo-generic.repository.ts:18:5 - error TS2322: Type 'Promise<UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>>' is not assignable to type 'Promise<T>'.
  Type 'UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>' is not assignable to type 'T'.
    'T' could be instantiated with an arbitrary type which could be unrelated to 'UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>'.

return this._repository.findById(id).populate(this._populateOnFind).exec();

For some reason I don't get it in your code but it arises in mine but the code is identical. Any ideas?

Using Framework import in core module

Hi,

Thanks for your article. I noticed that in your core module you are using element from class-validator and @nestjs/mapped-types. Does the core module shouldn't use that king of stuff

License

Is this under the MIT license... like nestjs?

Incompatible types

Hi,

great article but here's the problem I came across while looking through your code. In your MongoDataServices class you import your Author, Book and Genre from your mongo models but the abstract class IDataServices makes use of Author, Book and Genre that come from entites of the core directory. And these classes are different so my linter cries with errors.

How can I use transaction?

I read your article. It's easy to understand for me because of this Github repo.Thank you!
But I have a question about how I can implement transactions with clean architecture.

If you can, it would be helpful for other developers to add example codes using transition.

How to get Ids of entities

I have a question regarding entities. I can't see anything regarding Ids in the entities and the create as well as the get endpoints are returning elements of those entities. How can the clients then know which id they need to send to update/ delete for example an author or book?
I assume something is missing and it just works because although typescript does not know about the ids Mongoose returns them with the responses.

is it possible to merge

Hi, I have some questions
src/core/entities/book.entity.ts

and

src/frameworks/data-services/mongo/model/book.model.ts

Is it possible to merge?

If use typeorm don't need implement IGenericRepository?

thanks

TypeScript errors

Hi, nicely written article, however running the repo as-is according to the README, and I am seeing TypeScript errors.

These might be the same type mismatch errors reported in this issue:

#2

$ npm run start

> [email protected] start
> nest start

src/frameworks/data-services/mongo/mongo-data-services.service.ts:33:55 - error TS2345: Argument of type 'Model<AuthorDocument, {}, {}, {}, any>' is not assignable to parameter of type 'Model<Author, {}, {}, {}, any>'.
  Types of property 'bulkWrite' are incompatible.
    Type '{ (writes: AnyBulkWriteOperation<AuthorDocument>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }' is not assignable to type '{ (writes: AnyBulkWriteOperation<Author>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }'.
      Types of parameters 'writes' and 'writes' are incompatible.
        Type 'AnyBulkWriteOperation<Author>[]' is not assignable to type 'AnyBulkWriteOperation<AuthorDocument>[]'.
          Type 'AnyBulkWriteOperation<Author>' is not assignable to type 'AnyBulkWriteOperation<AuthorDocument>'.
            Type '{ insertOne: InsertOneModel<Author>; }' is not assignable to type 'AnyBulkWriteOperation<AuthorDocument>'.
              Type '{ insertOne: InsertOneModel<Author>; }' is not assignable to type '{ insertOne: InsertOneModel<AuthorDocument>; }'.
                The types of 'insertOne.document' are incompatible between these types.
                  Type 'OptionalId<Author>' is not assignable to type 'OptionalId<AuthorDocument>'.
                    Type 'OptionalId<Author>' is missing the following properties from type 'Pick<AuthorDocument, keyof Author | keyof Document>': title, close, normalize, URL, and 247 more.

33     this.authors = new MongoGenericRepository<Author>(this.AuthorRepository);
                                                         ~~~~~~~~~~~~~~~~~~~~~
src/frameworks/data-services/mongo/mongo-data-services.service.ts:34:51 - error TS2345: Argument of type 'Model<BookDocument, {}, {}, {}, any>' is not assignable to parameter of type 'Model<Book, {}, {}, {}, any>'.
  Types of property 'bulkWrite' are incompatible.
    Type '{ (writes: AnyBulkWriteOperation<BookDocument>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }' is not assignable to type '{ (writes: AnyBulkWriteOperation<Book>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }'.
      Types of parameters 'writes' and 'writes' are incompatible.
        Type 'AnyBulkWriteOperation<Book>[]' is not assignable to type 'AnyBulkWriteOperation<BookDocument>[]'.
          Type 'AnyBulkWriteOperation<Book>' is not assignable to type 'AnyBulkWriteOperation<BookDocument>'.
            Type '{ insertOne: InsertOneModel<Book>; }' is not assignable to type 'AnyBulkWriteOperation<BookDocument>'.
              Type '{ insertOne: InsertOneModel<Book>; }' is not assignable to type '{ insertOne: InsertOneModel<BookDocument>; }'.
                The types of 'insertOne.document' are incompatible between these types.
                  Type 'OptionalId<Book>' is not assignable to type 'OptionalId<BookDocument>'.
                    Type 'OptionalId<Book>' is missing the following properties from type 'Pick<BookDocument, "title" | "publishDate" | "close" | "normalize" | "author" | "genre" | "URL" | "alinkColor" | "all" | "anchors" | "applets" | "bgColor" | "body" | ... 244 more ... | "evaluate">': close, normalize, URL, alinkColor, and 246 more.

34     this.books = new MongoGenericRepository<Book>(this.BookRepository, [
                                                     ~~~~~~~~~~~~~~~~~~~
src/frameworks/data-services/mongo/mongo-data-services.service.ts:38:53 - error TS2345: Argument of type 'Model<GenreDocument, {}, {}, {}, any>' is not assignable to parameter of type 'Model<Genre, {}, {}, {}, any>'.
  Types of property 'bulkWrite' are incompatible.
    Type '{ (writes: AnyBulkWriteOperation<GenreDocument>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }' is not assignable to type '{ (writes: AnyBulkWriteOperation<Genre>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }'.
      Types of parameters 'writes' and 'writes' are incompatible.
        Type 'AnyBulkWriteOperation<Genre>[]' is not assignable to type 'AnyBulkWriteOperation<GenreDocument>[]'.
          Type 'AnyBulkWriteOperation<Genre>' is not assignable to type 'AnyBulkWriteOperation<GenreDocument>'.
            Type '{ insertOne: InsertOneModel<Genre>; }' is not assignable to type 'AnyBulkWriteOperation<GenreDocument>'.
              Type '{ insertOne: InsertOneModel<Genre>; }' is not assignable to type '{ insertOne: InsertOneModel<GenreDocument>; }'.
                The types of 'insertOne.document' are incompatible between these types.
                  Type 'OptionalId<Genre>' is not assignable to type 'OptionalId<GenreDocument>'.
                    Type 'OptionalId<Genre>' is missing the following properties from type 'Pick<GenreDocument, "name" | keyof Document>': title, close, normalize, URL, and 247 more.

38     this.genres = new MongoGenericRepository<Genre>(this.GenreRepository);
                                                       ~~~~~~~~~~~~~~~~~~~~
src/frameworks/data-services/mongo/mongo-generic-repository.ts:18:5 - error TS2322: Type 'Promise<UnpackedIntersection<HydratedDocument<T, {}, {}>, {}>>' is not assignable to type 'Promise<T>'.
  Type 'UnpackedIntersection<HydratedDocument<T, {}, {}>, {}>' is not assignable to type 'T'.
    'T' could be instantiated with an arbitrary type which could be unrelated to 'UnpackedIntersection<HydratedDocument<T, {}, {}>, {}>'.

18     return this._repository.findById(id).populate(this._populateOnFind).exec();
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Found 4 error(s).

How to add Sequelize?

Hi, thank you for your work.
How can I replace mongo with sequelize?

I've created a data-services/mysql/mysql.module following the example of mongo you provided.
However I get a lot of Type errors like:
Property 'findOneById' does not exist on type 'Model<T, T>'.ts(2339)

Can you provide an example?

Thanks

Add env.example file

Hi,

I went through your work. Sincerely, I love what you have done so far.

Can you add an env.example file to show the env variables and their types. This will help reduce the complexities involved in using the env variables as the codebase grows.

NestJs dependency at the use-case level

Hey @royib, thanks for this repo, this is a very nice starting point for discussion.

I see that use-cases have a dependency on NestJs (like here for example). I expected to only find dependencies on the core entities on this layer and then another layer on top of use-case where framework dependencies starts to appear, but I think something is missing.

Could you expand a bit on the whys of your implementation please? I'd greatly appreciate it!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.