Docs y prueba de concepto de colas por empresa

This commit is contained in:
2026-01-16 13:13:45 +01:00
parent b29348b88d
commit 51cfae7572
19 changed files with 358 additions and 1 deletions

View File

@@ -3,3 +3,20 @@
Monorepo de servicios / workers para centralizar los procesos de las SIM con sus subscripciones
[[./imgs/diagrama-servicios-sim.png]]
El objetivo es que al lanzar peticiones REST a la parte visible, que se
comprueben y se manden al broker para que los servicios de las compañías
los puedan consumir.
La idea es que las peticiones de activación, pausa, etc. no necesiten
tener una compañía especificada.
## Decisiones pendientes
- [ ] La capa worker según acción y la de operaciones de proveedores
se podrían unir en una sola con un enrutamiento por acción y compañía
pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`.
- [ ] La estructura de RMQ se genera por medio del JSON, igual habría que
definir cada cola en el worker que la consuma para poder añadir
workers sin parar el RMQ.
- [ ] Versionado de la API.

16
docs/sim-api/Activate.bru Normal file
View File

@@ -0,0 +1,16 @@
meta {
name: Activate
type: http
seq: 1
}
post {
url: {{baseurl}}/sim/activate
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

16
docs/sim-api/Cancel.bru Normal file
View File

@@ -0,0 +1,16 @@
meta {
name: Cancel
type: http
seq: 1
}
post {
url: {{baseurl}}/sim/cancel
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

16
docs/sim-api/Pause.bru Normal file
View File

@@ -0,0 +1,16 @@
meta {
name: Pause
type: http
seq: 1
}
post {
url: {{baseurl}}/sim/pause
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,16 @@
meta {
name: Preactivate
type: http
seq: 1
}
post {
url: {{baseurl}}/sim/preactivate
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

9
docs/sim-api/bruno.json Normal file
View File

@@ -0,0 +1,9 @@
{
"version": "1",
"name": "sim-api",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

View File

@@ -0,0 +1,3 @@
vars {
baseurl: http://locahost
}

View File

@@ -8,4 +8,5 @@ export interface EventBus {
consume(queue: string, callback: (msg: ConsumeMessage | null) => void): void;
ack(msg: ConsumeMessage): void;
nack(msg: ConsumeMessage): void;
}

View File

@@ -37,6 +37,11 @@ export class RabbitMQEventBus implements EventBus {
return this.channel.ack(msg)
}
nack(msg: ConsumeMessage) {
if (this.channel == undefined) throw new Error("[RMQ] Canal no iniciallizado");
return this.channel.nack(msg)
}
connection?: ChannelModel
channel?: ConfirmChannel
connected: Boolean = false

View File

@@ -0,0 +1,22 @@
PORT=3000
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
ENVIORMENT=development
RABBITMQ_HOST=rabbitmq-sim-broker
#RABBITMQ_HOST=localhost
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_SECURE=false
RABBITMQ_VHOST=sim-vhost
# Hay cosas que unificar de varios servicios
POSTGRES_DB=postgres
POSTGRES_DATABASE=postres
POSTGRES_HOST=postgresql-sim-1
POSTGRES_PORT=5432
DEV_POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=1234

View File

@@ -0,0 +1,65 @@
import { EventBus } from "#shared/domain/EventBus.port";
import { ConsumeMessage } from "amqplib";
export class SimNosController {
private eventBus: EventBus;
private activationUseCases: any;
private routes = new Map<string, () => void>([
["activate", async () => { console.log("caso de uso activate") }],
["pause", async () => { console.log("caso de uso pause") }],
["cancel", async () => { console.log("caso de uso cancel") }],
])
constructor(
eventBus: EventBus
) {
this.eventBus = eventBus
// No se si hay un sistema mejor
// convertor en const () => {} para conservar el contexto??
this.recibeMsg = this.recibeMsg.bind(this)
}
public async recibeMsg(msg: ConsumeMessage | null) {
if (!this.validateActivationMsg(msg)) {
throw new Error("Error consumiendo el mensaje no es valido")
}
msg = msg!
const msgParsed = JSON.parse(String(msg.content))
const msgKey = msg.fields.routingKey.split(".")
const accion = msgKey[2]
if (accion == undefined) {
console.error("La routingKey es incorrecta: " + accion)
this.eventBus.nack(msg)
return;
}
if (this.routes.get(accion) == undefined) {
console.error("No hay una ruta definida para la accion")
this.eventBus.nack(msg)
return;
}
try {
this.routes.get(accion)!()
} catch (err) {
console.log("Error procesando el mensaje")
this.eventBus.nack(msg)
} finally {
this.eventBus.ack(msg)
}
}
/**
* TODO:
* - Loguear motivos de la no validacion
*/
private validateActivationMsg(msg: ConsumeMessage | null) {
if (msg == undefined) return false;
return true;
}
}

View File

@@ -0,0 +1,22 @@
import { loadEnvFile } from "node:process";
loadEnvFile("../../.env")
export const env = {
ENVIRONMENT: process.env.ENVIORMENT,
POSTGRES_USER: process.env.POSTGRES_USER,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
POSTGRES_PORT: process.env.POSTGRES_PORT,
POSTGRES_HOST: process.env.POSTGRES_HOST,
POSTGRES_DATABASE: process.env.POSTGRES_DATABASE,
RABBITMQ_HOST: String(process.env.RABBITMQ_HOST ?? "localhost"),
RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "guest"),
RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "guest"),
RABBITMQ_EXCHANGE: String(process.env.RABBITMQ_EXCHANGE ?? "/"),
RABBITMQ_PORT: parseInt(process.env.RABBITMQ_PORT ?? "5672"),
RABBITMQ_MODULENAME: process.env.MODULENAME,
RABBITMQ_TTL: process.env.RABBITMQ_TTL,
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
};

View File

@@ -0,0 +1,36 @@
import { RabbitMQEventBus, RMQConnectionParams } from "#shared/infrastructure/RabbitMQEventBus"
import { env } from "./env"
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
export const rmqConnOptions = <RMQConnectionParams>{
username: rmqUser,
password: rmqPass,
vhost: rmqVhost,
hostname: rmqHost,
port: rmqPort,
secure: rmqSecure,
}
export const rabbitmqEventBus = new RabbitMQEventBus({
connectionParams: rmqConnOptions
})
export async function startRMQClient() {
await rabbitmqEventBus.connect()
// Bindings especificos, deberia meterlos en la clase
try {
rabbitmqEventBus.channel?.assertQueue("sim.nos")
} catch {
console.log("[i] Cola de sims de nos creada")
rabbitmqEventBus.channel?.bindQueue("sim.nos", "sim.exchange", "sim.nos.*")
}
return rabbitmqEventBus
}

View File

@@ -0,0 +1,22 @@
import { startRMQClient } from "#config/eventBusConfig"
import { SimNosController } from "aplication/SimNOS.controller"
async function startWorker() {
const rmqClient = await startRMQClient()
const simController = new SimNosController(
rmqClient
)
rmqClient.consume("sim.nos", simController.recibeMsg)
}
startWorker()
.then(e => {
console.log("[o] Worker de SIM de NOS iniciado")
})
.catch(e => {
console.log("[x] Error iniciando worker de SIM de NOS")
})
export default {}

View File

@@ -0,0 +1,32 @@
{
"name": "sim-consumidor-nos",
"version": "1.0.0",
"description": "consumidor generico de eventos de NOS",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "tsx watch index.ts "
},
"author": "",
"license": "ISC",
"packageManager": "yarn@4.12.0",
"dependencies": {
"@tsconfig/node22": "*",
"amqplib": "^0.10.9",
"cors": "*",
"dotenv": "*",
"express": "*",
"typescript": "*"
},
"devDependencies": {
"@types/amqplib": "^0.10.8",
"@types/cors": "*",
"@types/express": "*",
"@types/node": "*",
"@types/supertest": "*",
"prettier": "*",
"supertest": "*",
"tsx": "*",
"vitest": "*"
}
}

View File

@@ -0,0 +1,37 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/sim-consumidor-nos",
"baseUrl": ".",
"paths": {
"#config/*": [
"config/*"
],
"#adapters/*": [
"adapters/*"
],
"#domain/*": [
"domain/*"
],
"#ports/*": [
"ports/*"
],
"#tests/*": [
"__tests__/*"
],
"#shared/*": [
"../shared/*"
],
}
},
"exclude": [
"node_modules"
],
"include": [
"**/*.ts",
"src/**/*.d.ts"
],
"files": [
"index.ts"
]
}

View File

@@ -1,6 +1,6 @@
import { rabbitmqEventBus } from '#config/eventBusConfig';
import { SimUsecases } from 'aplication/Sim.usecases';
import { SimController } from 'aplication/SimController';
import { SimController } from 'aplication/Sim.controller';
import { Router } from 'express';
const simRoutes = Router()

View File

@@ -2284,6 +2284,28 @@ __metadata:
languageName: node
linkType: hard
"sim-consumidor-nos@workspace:packages/sim-consumidor-nos":
version: 0.0.0-use.local
resolution: "sim-consumidor-nos@workspace:packages/sim-consumidor-nos"
dependencies:
"@tsconfig/node22": "npm:*"
"@types/amqplib": "npm:^0.10.8"
"@types/cors": "npm:*"
"@types/express": "npm:*"
"@types/node": "npm:*"
"@types/supertest": "npm:*"
amqplib: "npm:^0.10.9"
cors: "npm:*"
dotenv: "npm:*"
express: "npm:*"
prettier: "npm:*"
supertest: "npm:*"
tsx: "npm:*"
typescript: "npm:*"
vitest: "npm:*"
languageName: unknown
linkType: soft
"sim-consumidor@workspace:packages/sim-consumidor-activaciones":
version: 0.0.0-use.local
resolution: "sim-consumidor@workspace:packages/sim-consumidor-activaciones"