Skip to content

Exception-Corporation/users-ddd-service

Repository files navigation

Challenge

Image text

πŸ’» I'm a dev, how do I get started?

Prerequisites:

ENDPOINTS

Import the postman json file

  • [GET] http://localhost:4000/api/v1/users/getAll Get all the users
  • [GET] http://localhost:4000/api/v1/users/get/:id Get one user by id
  • [POST] http://localhost:4000/api/v1/users/ Create new normal user
  • [POST] http://localhost:4000/api/v1/users/login Get a token to authenticate in other services
  • [PUT] http://localhost:4000/api/v1/users/update/:id Update a user by id
  • [DELETE] http://localhost:4000/api/v1/users/delete/:id Delete a user by id

Now:

Create a .env file with the .env.example text

Steps to follow in sequential order

git clone git@github.com:Exception-Corporation/users-api.git
cd users-api
yarn
yarn containers:up # run the docker-compose
yarn containers:down # remove the containers (ignore)
sh postgres.loading.sh localhost
yarn start #compile typescript to javascript and run the api in production mode (Without pm2)
yarn dev #run the api in development mode with nodemon

STOP POSSIBLE POSTGRES INSTANCE (MACOS)

  • brew services stop postgresql

COMMAND TO INITIALIZE TYPEORM WITH POSTGRESQL (UNNECESSARY FOR YOU - DON'T RUN THIS COMMAND IN THIS PROJECT)

  • npx typeorm init --database postgres --express

You are now good ready to go!! πŸ‘―

Docker

We use Docker as a utility tool, mainly for running a PostgresDB, which is the Database that we have deploy in Postgresdb atlas. In the docker-compose.yml you have three services:

  • postgres-test: A Postgres database that we use for starting the API in development mode and running the integration tests locally.
  • postgres: A Postgres database that we use for starting the API in production mode.
  • pgadmin: A system managment to use the UI with postgres.
  • redis: A cache database that we use for starting the API in production mode.
  • redis-commander: A system managment to use the UI with redis.
  • rabbitmq: Messaging service for an event-driven architecture.

Project management

  • Challenge
  • Github repo
  • Github Actions
  • TODO: Software Architecture
  • Clean Architecture
  • Hexagonal Architecture
  • Onion Architecture
  • Object Oriented
  • DDD: Domain Driven Design
  • TODO: PostgresDB

πŸ›  Which technologies are you using?

  • Node
    • Validations with [Class Validator & Class Transformer]
    • Mainly used as dependency injection container
  • TypeScript
  • Inversify

🏘 How is the code organized?

The architecture follows the principles from Hexagonal Architecture, and the final implementation is inspired by this and this repositories from CodelyTV.

All the main code of the application lives under src

user/shared or any module

Under this directory lives all the main application. This root directory contains all the modules of the app, and inside of each module you can find the classic division domain/application/infrastructure.

  • Domain: All the classes needed for modeling the business.
  • Application (AKA Application) (Use-Cases): These are specific use cases which orchestrates several domain elements to perform its job.
  • Infrastructure: All the elements that are coupled to a certain Database/Library/Framework.

For example:

TODO: place a simple tree when the project has evolved

πŸ•΄ Dependency Injection, Dependency Inversion

Instead of depending on a certain implementation, we depend on an abstraction (an interface). This allows us to create a more decoupled architecture and facilitates testing.

It's the D from the SOLID principles.

You can read more about dependency inversion here.

  • Do not import third-parties or side effect methods into the domain/use cases layer
  • Instead, create an interface that represent that interaction

Dependency injection container

For wiring up all the dependencies, we are using the native NodeJs dependency container. This is the only thing that we are coupled to, specially from the application layer.

A special thing that we have to take into account, is when injecting interfaces.

The interfaces are a compile-time thing of Typescript, so when we need to inject a certain implementation we need to specify an identifier for that interface with a token.

interface AccountRepository {
  save(account: Account): Promise<void>;
}
import 'reflect-metadata';
import { injectable, container, inject } from '@container';

interface Weapon {
  hit(): string;
}

interface ThrowableWeapon {
  throw(): string;
}

const TYPES = {
  katana: Symbol.for('Katana'),
  Shuriken: Symbol.for('Shuriken')
};

@provide(TYPES.katana)
class Katana implements Weapon {
  public hit() {
    return 'hit!';
  }
}

@provide(TYPES.Shuriken)
class Shuriken implements ThrowableWeapon {
  public throw() {
    return 'throw!';
  }
}

@injectable()
class Main {
  constructor(
    @inject(TYPES.katana) private readonly katana: Weapon,
    @inject(TYPES.Shuriken) private shuriken: ThrowableWeapon
  ) {}

  get() {
    console.log(this.katana.hit());
    console.log(this.shuriken.throw());
  }
}

const main = container.resolve(Main);
main.get();
// Reflects all decorators provided by this package and packages them into
// a module to be loaded by the container

βœ… Tests

CI/CD

  • The CI and CD are in Github Actions
  • We run the precommit script before each commit using Husky.
  • The CI runs for both acceptance/unitary and integration tests with a real database.
  • After all tests passed, then the API is re-deployed

πŸ“² Contact

The project was mainly developed by Edgar Castillo Vega