Ticker

6/recent/ticker-posts

API Node with Hexagonal Architecture + Clean Architecture + DDD

 


Hexagonal Architecture
 
The Hexagonal Architecture proposes that our domain is the core of the layers and that it is not coupled to anything external. Instead of making explicit use of the principle of dependency inversion, we couple to contracts (interfaces or ports) and not to concrete implementations.
 
Clean Architecture
 
Clean Architecture is a term introduced by Robert C. Martin, better known as Uncle Bob. He compiled the most used layered models in an improved version that he called Clean Architecture, basically it offers a series of best practices when designing our application, where the domain should be at the center and should not depend on anything external (Infrastructure).

DDD

They are a set of patterns, principles and practices that help us to understand, solve and design the best solutions to business problems (Domain), applying object-oriented design. Focusing first on discovering the objects of our domain in order to continue with the design of the other layers of our application.
 

It can be concluded that the concepts are strongly related, so whichever one is implemented is a good practice. In these architectures the following layers stand out:

Domain

It can contain any Interface, Type, Enum or Constant that is important to our business, and can define the operation of the outermost layers.
Entities: These are interfaces that are the representation of our business objects, for example: Users, Products and Orders. 
Repositories: They are interfaces that define the methods with our entities, for example: findAll, findByid, create, update, delete.
 
Application  

This layer must contain all the business logic. They are Classes or functions that interact with our domain entities, according to your preference some call them use cases or other services. These classes must implement repositories or any class injected as a dependency.
 
Infrastructure  

It is the last layer of our architecture in it must be: Frameworks, Libraries. These implement the input and output connections of our application such as: Http, Graphql, Databases.

Repositories: They usually implement the connections to data sources, such as databases or APIs.  
Input and output: receive and send the data of our application generally called Controllers.


The following is an example of an API code with this architecture.


Structure Project

	
─ src
  ├─ application
  │  └─ services
  │     └─ users.service.ts
  ├─ domain
  │  ├─ entities
  │  │  └─ users.entity.ts
  │  └─ respositories
  │     └─ users.repository.ts
  └─ infrastructure
     ├─ express
     │  ├─ controllers
     │  │  └─ users.controller.ts
     │  └─ server
     │     └─ server.express.ts
     └─ sequelize
        ├─ config.sequelize.ts
        ├─ models
        │  └─ users.model.ts
        └─ respositories
           └─ users.repository.ts
	

Dependencies required for this project

	
npm i -D @types/express @types/node @types/validator @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint nodemon prettier typescript

npm i express reflect-metadata sequelize sequelize-typescript sqlite3
	

Domain -> Entities -> user.entity.ts

	
export interface IUser {
  id: number
  username: string
  password: string
  firstName: string
  lastName: string
}
	

Domain -> Repositories-> users.repository.ts

	
import { IUser } from "../entities/users.entity"

// get one user
type result = [IUser | null, Error | null]
// get many users
type results = [IUser[] | null, Error | null]

export interface IUsersRepository {
  findAll: () => Promise
  findById: (id: number) => Promise
  create: (user: any) => Promise
}
	

Application -> Services -> users.service.ts

	
import { IUser } from "../../domain/entities/users.entity"
import { IUsersRepository } from "../../domain/respositories/users.repository"
import { UsersRepositoryIns } from "../../infrastructure/sequelize/respositories/users.repository"

class UsersService {
  _repository: IUsersRepository
  constructor(repository: IUsersRepository) {
    this._repository = repository
  }

  async findAll() {
    const [users, err] = await this._repository.findAll()
    if (err) {
      return { code: 500, data: "error getting users " }
    }

    return { code: 201, data: users }
  }

  async findById(id: number) {
    const [user, err] = await this._repository.findById(id)
    if (err) {
      return { code: 500, data: "error getting the user" }
    }

    if (!user) {
      return { code: 404, data: "user not found" }
    }

    return { code: 201, data: user }
  }

  async create(userData: IUser) {
    const [user, err] = await this._repository.create(userData)

    if (err) {
      return { code: 500, data: "error creating the user" }
    }
    return { code: 201, data: user }
  }
}

// Inject Repository
export const UsersServiceIns = new UsersService(UsersRepositoryIns)
	

Infrastructure -> Sequelize -> Repositories -> users.repository.ts

	
import { IUser } from "../../../domain/entities/users.entity"
import { IUsersRepository } from "../../../domain/respositories/users.repository"
import Users from "../models/users.model"

class UsersRepository implements IUsersRepository {
  async findAll() {
    try {
      const user = await Users.findAll()
      return [user, null] as [IUser[], null]
    } catch (e) {
      console.error(e)
      return [null, e] as [null, Error]
    }
  }

  async findById(id: number) {
    try {
      const user = await Users.findByPk(id)
      return [user, null] as [IUser, null]
    } catch (e) {
      console.error(e)
      return [null, e] as [null, Error]
    }
  }

  async create(userData: IUser) {
    try {
      const user = await Users.create(userData)
      return [user, null] as [IUser, null]
    } catch (e) {
      console.error(e)
      return [null, e] as [null, Error]
    }
  }
}

export const UsersRepositoryIns = new UsersRepository()

	

Infrastructure -> Sequelize -> Models -> users.model.ts

	
import { Table, Column, Model } from "sequelize-typescript"
import { IUser } from "../../../domain/entities/users.entity"

type IUserModel = Omit

@Table
export default class Users extends Model implements IUserModel {
  @Column
  username: string
  @Column
  password: string
  @Column
  firstName: string
  @Column
  lastName: string
}
	

Infrastructure -> Sequelize -> config.sequelize.ts

	
import { Sequelize } from "sequelize-typescript"

export const sequelizeIns = new Sequelize({
  dialect: "sqlite",
  models: [__dirname + "/models"],
  storage: __dirname + "/../../../db/app.db.sqlite",
  logging: false,
})

;(async () => {
  await sequelizeIns.sync({ alter: true })
  console.log("Model Sync......")
})()

	

Infrastructure -> Express -> Controllers -> users.controller.ts

	
import express from "express"
import { UsersServiceIns } from "../../../application/services/users.service"

export const UsersController = express.Router()

UsersController.get("/", async (req, res) => {
  const { code, data } = await UsersServiceIns.findAll()
  return res.status(code).json(data)
})

UsersController.get("/:id", async (req, res) => {
  const { code, data } = await UsersServiceIns.findById(+req.params.id)
  return res.status(code).json(data)
})

UsersController.post("/", async (req, res) => {
  const { code, data } = await UsersServiceIns.create(req.body)
  return res.status(code).json(data)
})

	

Infrastructure -> Express -> server.express.ts

	
import express from "express"
import "../../sequelize/config.sequelize"
import { UsersController } from "../controllers/users.controller"

const app = express()
app.use(express.json())
app.use(express.urlencoded())
const port = 3000

app.get("/", (_, res) => {
  res.send("API Clean Hexagonal DDD Architecture")
})

app.use("/users", UsersController)

app.listen(port, () => {
  console.log("Run server....", port)
})
	

this publication is an update to the previous publication on hexagonal architecture. https://fullkernel.blogspot.com/2021/09/hexagonal-architecture-api-with-node.html

Repository: https://github.com/sistemasnegros/api-clean-hexagonal-ddd-architecture

 

Referencias:

Alistair Cockburn - Articule https://alistair.cockburn.us

Robert C. Martin - Clean Arquitecture( Book).

Eric Evans - Domain-Driven Design (Book).

 

Reacciones:

Post a Comment

0 Comments