Hexagonal, mejora del cliente RMQ y tipos de eventos

This commit is contained in:
2026-01-13 15:41:59 +01:00
parent a6abc24e5f
commit d2db2062b0
20 changed files with 517 additions and 100 deletions

View File

@@ -0,0 +1,21 @@
/**
* Los eventos de dominion estan orientados a la cola AMQ
*
*/
// Completar con los tipos de evento
export type DomainEventType = string
export type DomainEvent = {
key: string,
payload: Object,
options: Object,
occurredOn: Date,
}
export interface DomainEventSubscriber<T extends DomainEvent> {
subscribedTo(): DomainEventType[];
getEventNames(): string[];
on(domainEvent: T): Promise<void>;
}

View File

@@ -0,0 +1,6 @@
import { DomainEvent, DomainEventSubscriber } from "./DomainEvent";
export interface EventBus {
publish(events: Array<DomainEvent>): Promise<void>;
addSubscribers(subscribers: Array<DomainEventSubscriber<DomainEvent>>): void;
}

View File

@@ -0,0 +1,39 @@
import { DomainEvent } from "./DomainEvent";
export namespace SimEvents {
export type activation = DomainEvent & {
key: "sim.activation",
payload: {
iccid: string
},
options: {
}
}
export type cancelation = DomainEvent & {
key: "sim.cancelation",
payload: {
iccid: string
},
options: {
}
}
export type pause = DomainEvent & {
key: "sim.pause",
payload: {
iccid: string
},
options: {
}
}
export type free = DomainEvent & {
key: "sim.free",
payload: {
iccid: string
},
options: {
}
}
}

View File

@@ -1,24 +1,56 @@
import client, { ChannelModel, ConfirmChannel, connect, Connection } from "amqplib"
import { type ChannelModel, type ConfirmChannel, connect as amqConnect } from "amqplib";
import { DomainEvent, DomainEventSubscriber } from "../domain/DomainEvent";
import { EventBus } from "../domain/EventBus.port";
import { buffer } from "node:stream/consumers";
export type RMQConnectionParams = {
username: string,
password: string,
vhost: string,
hostname: string,
port: number,
secure: boolean
}
const PREFETCH_LIMIT = 1
export class RabbitMQEventBus implements EventBus {
constructor(args: {
connectionParams: RMQConnectionParams
}) {
this.connectionOptions = args.connectionParams
this.checkStructure();
}
export class RabbitConnection {
connection?: ChannelModel
channel?: ConfirmChannel
connected: Boolean = false
private connectionOptions: {
hostname: string,
port: number,
secure: boolean,
username: string,
password: string,
vhost: string
private connectionOptions: RMQConnectionParams
public async connect() {
this.connection = await this.createConnection();
if (this.connection == undefined) throw new Error("[RMQ] Error crecreando la conexion")
this.channel = await this.createConfirmChannel()
}
constructor(opts: typeof this.connectionOptions) {
this.connectionOptions = opts
this.checkStructure();
publish(events: DomainEvent[]): Promise<void> {
return new Promise((res, rej) => {
try {
for (const event of events) {
const exchange = "sim.exchange"
const routingKey = event.key
const content = Buffer.from(JSON.stringify(event))
this.channel?.publish(exchange, routingKey, content)
}
return res()
} catch (err) {
return rej(err)
}
})
}
addSubscribers(subscribers: Array<DomainEventSubscriber<DomainEvent>>): void {
throw new Error("Method not implemented.");
}
/**
@@ -31,19 +63,13 @@ export class RabbitConnection {
this.channel?.assertExchange("sim.exchange", "direct")
}
public async connect() {
this.connection = await this.createConnection();
if (this.connection == undefined) throw new Error("[RMQ] Error crecreando la conexion")
this.channel = await this.createConfirmChannel()
}
protected async createConnection() {
const { hostname, port, secure } = { ...this.connectionOptions }
const { username, password } = { ...this.connectionOptions };
const protocol = secure ? 'amqps' : 'amqp';
const vhost = this.connectionOptions.vhost
const connection = await client.connect({
const connection = await amqConnect({
protocol,
hostname,
port,
@@ -75,4 +101,3 @@ export class RabbitConnection {
}
}

View File

@@ -21,4 +21,3 @@ export const env = {
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
};
console.log("Consumidor", env)

View File

@@ -11,7 +11,6 @@ const rmqVhost = env.RABBITMQ_VHOST
async function test() {
console.log("iniciado Consumidor", env)
const rbmq = new RabbitConnection({
username: rmqUser,
password: rmqPass,
@@ -26,6 +25,8 @@ async function test() {
await rbmq.channel?.consume("sim.queue", (buff: ConsumeMessage | null) => {
const decoded = buff?.content.toString()
console.log(" [Consumidor] Mensaje recibido ", decoded)
}, {
noAck: true
})
}

View File

@@ -0,0 +1,42 @@
import { EventBus } from "../../shared/domain/EventBus.port";
import { SimEvents } from "../../shared/domain/SimEvents";
/**
* TODO:
* - Conexion con la BDD
* - Conexion con RabbitMQ
* - Pasar a clase cuando existan las conexiones
*/
export class SimUsecases {
private eventBus: EventBus
constructor(args: {
eventBus: EventBus
}
) {
this.eventBus = args.eventBus
}
async activation(args: { iccid: string }) {
const activationEvent = <SimEvents.activation>{
key: "sim.activation",
payload: {
iccid: args.iccid
}
}
return this.eventBus.publish([activationEvent])
}
cancelation(args: { iccid: string }) {
throw new Error("Function not implemented.");
}
pause(args: { iccid: string }) {
throw new Error("Function not implemented.");
}
free(args: { iccid: string }) {
throw new Error("Function not implemented.");
}
}

View File

@@ -0,0 +1,37 @@
import { Request, Response } from "express"
import { SimUsecases } from "aplication/Sim.usecases"
export class SimController {
private simUseCases: SimUsecases
constructor(args: {
simUseCases: SimUsecases
}) {
this.simUseCases = args.simUseCases
}
async activation(req: Request, res: Response) {
const { iccid } = req.body
if (iccid == undefined) {
// TODO: excepcion con nombre se va a repetir
res.status(400).json({
msg: "iccid invalido"
})
}
const resp = await this.simUseCases.activation({ iccid })
}
cancelation(req: Request, res: Response) {
}
pause(req: Request, res: Response) {
}
free(req: Request, res: Response) {
}
}

View File

@@ -0,0 +1,50 @@
import { env } from "#config/env"
import { SimEvents } from "#shared/domain/SimEvents"
import { RabbitMQEventBus, RMQConnectionParams } from "#shared/infrastructure/RabbitMQEventBus"
const rmqUser = env.RABBITMQ_USER
const rmqPass = env.RABBITMQ_PASSWORD
const rmqHost = env.RABBITMQ_HOST
const rmqPort = Number(env.RABBITMQ_PORT)
const rmqSecure = false
const rmqVhost = env.RABBITMQ_VHOST
async function test() {
const connOptions = <RMQConnectionParams>{
username: rmqUser,
password: rmqPass,
vhost: rmqVhost,
hostname: rmqHost,
port: rmqPort,
secure: rmqSecure,
}
const event = <SimEvents.activation>{
key: "sim.activation",
payload: {
iccid: "1234"
},
options: {
}
}
const rmqCli = new RabbitMQEventBus({
connectionParams: connOptions
})
await rmqCli.connect()
console.log("publicando", event)
rmqCli.publish([event])
.then(e => {
console.log("Mensaje publicado", e)
})
.catch(err => console.error)
}
test()
export default {}

View File

@@ -0,0 +1,22 @@
import { Router } from 'express';
const simRoutes = Router()
simRoutes.get("/status")
simRoutes.post("/save", (req, res) => {
})
simRoutes.post("/activate", (req, res) => {
const { iccid } = req.body
})
simRoutes.post("/pause", (req, res) => {
})
simRoutes.post("/cancel", (req, res) => {
})
// Proceso especifico de ALAI para liberar sims canceladas
simRoutes.post("/free", (req, res) => {
})

View File

@@ -1,5 +1,5 @@
{
"name": "sim-gestor-eventos",
"name": "sim-entrada-eventos",
"version": "1.0.0",
"description": "",
"main": "index.ts",

View File

@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/sim-gestor-eventos",
"outDir": "../../dist/sim-entrada-eventos",
"baseUrl": ".",
"paths": {
"#config/*": [

View File

@@ -1,42 +0,0 @@
import { env } from "#config/env"
import { RabbitConnection } from "#shared/adapters/queues/RabbitMQClient"
const rmqUser = env.RABBITMQ_USER
const rmqPass = env.RABBITMQ_PASSWORD
const rmqHost = env.RABBITMQ_HOST
const rmqPort = Number(env.RABBITMQ_PORT)
const rmqSecure = false
const rmqVhost = env.RABBITMQ_VHOST
console.log("[Generador] Iniciando")
async function test() {
const rbmq = new RabbitConnection({
username: rmqUser,
password: rmqPass,
vhost: String(rmqVhost),
hostname: rmqHost,
port: rmqPort,
secure: rmqSecure
})
await rbmq.connect()
console.log("[Generador] enviando --")
rbmq.channel?.sendToQueue("sim.queue", Buffer.from("test"), {},
function (err, ok) {
if (err !== null)
console.warn('Message nacked!');
else
console.log('Message acked');
}
)
}
test()
export default {}