Hexagonal Architecture
- Clean Architecture by Uncle Bob.
- Onion Architecture.
- Separate infrastructure from business logic.
- Inputs and outputs in border the our design.
Layers
- Domain -> Entities: Represent business objects (Interfaces).
- Domain -> Interactors or Services: Functions that interact with entities, business logic .
- Domain -> Repositories: Interfaces that have operations with entities, for example, getbyId, getAll, save.
- Aplication -> Transport Layer: Communication methods, for example, HTTP or SQS (Input andOutput).
- Infratructure -> Data Sources: Class that implements the functions of the repositories.
Install dependencies
npm install -D @types/express @types/node @types/validator ts-node typescript
npm install express mysql2 sequelize
Structure Project
├─ src
│ ├─ controllers
│ │ └─ http
│ │ └─ PokemonController.ts
│ ├─ core
│ │ ├─ const
│ │ │ └─ http.ts
│ │ ├─ entities
│ │ │ └─ Pokemon.ts
│ │ ├─ interactors
│ │ │ ├─ IResponse.ts
│ │ │ ├─ index.ts
│ │ │ └─ pokemonInteractor.ts
│ │ └─ repositories
│ │ └─ pokemonRepository.ts
│ ├─ dataSources
│ │ ├─ PokemonDataSource.ts
│ │ └─ sequelize
│ │ ├─ PokemonModel.ts
│ │ └─ index.ts
│ └─ server
│ └─ index.ts
Endpoint app src/server/index.ts
import express from 'express';
import { Request, Response } from 'express';
import PokemonController from '../controllers/http/PokemonController';
const app = express();
app.get('/', (req: Request, res: Response) => {
res.send('Application works!');
});
app.use('/api/v1/pokemon', PokemonController);
app.listen(3000, () => {
console.log('Application started on port 3000!');
});
src/controllers/http/PokemonController.ts
import PokemonInteractor from '../../core/interactors';
import express from 'express';
import { Response, Request } from 'express';
const router = express.Router();
router.get('/:id', async (req: Request, res: Response) => {
const { id } = req.params;
const { status, data, error } = await PokemonInteractor.getById(parseInt(id));
res.status(status).json({ data, error });
});
router.get('/', async (req: Request, res: Response) => {
const { status, data, error } = await PokemonInteractor.getAll();
res.status(status).json({ data, error });
});
export default router;
src/core/interactors/index.ts
import PokemonInteractor from './pokemonInteractor';
import PokemonDataSource from '../../dataSources/PokemonDataSource';
const pokemonDataSource = new PokemonDataSource();
const pokemonInteractor = new PokemonInteractor(pokemonDataSource);
export default pokemonInteractor;
src/core/interactors/pokemonInteractor.ts
import PokemonRepository from '../repositories/pokemonRepository';
import PokemonEntity from '../entities/Pokemon';
import { HTTP_STATUS } from '../const/http';
import { IResponse } from './IResponse';
class PokemonInteractor {
pokemonRepository: PokemonRepository;
constructor(pokemonRepository: PokemonRepository) {
this.pokemonRepository = pokemonRepository;
}
async getById(id: number): Promise<IResponse<PokemonEntity | undefined>> {
const pokemonModel: PokemonEntity = await this.pokemonRepository.getById(
id,
);
if (!pokemonModel) {
return { status: HTTP_STATUS.NOT_FOUND, error: 'not found' };
}
const pokemon: PokemonEntity = {
id: pokemonModel.id,
name: pokemonModel.name,
type: pokemonModel.type,
image: pokemonModel.image,
};
return { status: HTTP_STATUS.OK, data: pokemon };
}
async getAll(): Promise<IResponse<PokemonEntity[]>> {
try {
const pokemonModel = await this.pokemonRepository.getAll();
return { status: HTTP_STATUS.OK, data: pokemonModel };
} catch (e) {
return { status: HTTP_STATUS.INTERNAL_ERROR, error: e.message };
}
}
}
export default PokemonInteractor;
src/core/reporsitories/pokemonRepository.ts
import Pokemon from '../entities/Pokemon';
interface PokemonRepository {
getById(id: number): Promise<Pokemon>;
getAll(): Promise<Pokemon[]>;
}
export default PokemonRepository;
src/core/entities/pokemon.ts
export default interface Pokemon {
id: number;
name: string;
type: string;
image: string;
}
src/dataSources/PokemonDataSource.ts
import pokemonRepository from '../core/repositories/pokemonRepository';
import Pokemon from '../core/entities/Pokemon';
import PokemonModel from './sequelize/PokemonModel';
class PokemonDataSource implements pokemonRepository {
public async getById(id: number): Promise<Pokemon> {
const pokemonModel: Pokemon = await PokemonModel.findOne({ where: { id } });
return pokemonModel;
}
public async getAll(): Promise<Pokemon[]> {
const pokemonModel: Pokemon[] = await PokemonModel.findAll();
return pokemonModel;
}
}
export default PokemonDataSource;
src/dataSources/sequelize/PokemonModel.ts
import { Model, DataTypes, Optional } from 'sequelize';
import PokemonAttributes from '../../core/entities/Pokemon';
import sequelizeInstance from './index';
interface PokemonCreationAttributes extends Optional<PokemonAttributes, 'id'> {}
class Pokemon
extends Model<PokemonAttributes, PokemonCreationAttributes>
implements PokemonAttributes
{
public id!: number;
public name!: string;
public image!: string;
public type!: string;
// timestamps!
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
Pokemon.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: new DataTypes.STRING(128),
allowNull: false,
},
image: {
type: new DataTypes.STRING(128),
allowNull: false,
},
type: {
type: new DataTypes.STRING(128),
allowNull: false,
},
},
{
tableName: 'pokemon',
sequelize: sequelizeInstance,
},
);
export default Pokemon;
src/dataSources/sequelize/index.ts
import { Sequelize } from 'sequelize';
const sequelize = new Sequelize(
'mysql://unsername:password@localhost:3306/pokemonDb',
);
export default sequelize;
Git Repository: https://github.com/sistemasnegros/api-hex-arch-express-types
1 Comments
Excelente artÃculo! bien explicado, bien detallado además de bien estructurado el proyecto. Muchas felicidades!!
ReplyDelete