Loading home…
Loading home…
Loading article…
Seis cajas en el diagrama. Seis tentaciones de microservicio. Casi caí otra vez hasta entender que el mapa completo no obliga a desplegarlo entero la primera semana, y que separar repos no separa el dominio.

Sin spam — solo un aviso cuando publique algo nuevo sobre backend, cloud y arquitectura.
Un email cuando salga un artículo. Puedes darte de baja cuando quieras.

Un post de LinkedIn sobre arquitectura fintech, un reto técnico reciente y la decisión de reconstruir un core bancario desde cero — más lento, para aprender de verdad.

Tres segundos de spinner. Timeout en pantalla. ¿Se movió el dinero? Reabrí mi challenge de Arkano con un mapa de arquitectura fintech al lado y entendí por qué un POST /transfer nunca fue solo un endpoint.

Si agregar un nuevo cliente implica desplegar una nueva aplicación o copiar una base de datos completa, probablemente tu arquitectura SaaS no está preparada para escalar. Aprende cómo implementar multi-tenancy en NestJS de forma limpia y mantenible
En el artículo anterior cerré con una idea que todavía me sirve de brújula: el mapa completo no implica desplegarlo entero la primera semana. Lo que sí implica es no tirar todo en un controller. Y hay una trampa intermedia: creer que porque separaste repos ya separaste el dominio.
Spoiler: no siempre.
En arkano-banking-challenge me pasó lo de siempre cuando vas contra reloj: un servicio que hace de todo y un controller que lo sabe. Funcionó para entregar. Para explicar, no tanto.
Al reconstruir en fintech-core-platform el otro extremo me miraba desde el README: microservicios desde el día uno, diagrama enterprise, impresión en LinkedIn. Bonito en captura. Pesado de operar cuando eres tú solo con el teclado y sin trazas distribuidas montadas.
El punto medio que me convence ahora es aburrido de decir en conferencia pero honesto de mantener: un monolito modular. Un artefacto. Una PostgreSQL al inicio. Módulos que no se importan en diagonal y dominio que no conoce NestJS.
No es renunciar a microservicios. Es no pagar su factura antes de tiempo.
No me gustan los posts que dibujan hexagonal con diez cajas vacías. En la práctica pienso en cuatro cosas:
El dominio dice qué está permitido: no suspendes una cuenta cerrada, no inventas reglas en el controller. La aplicación coordina: abrir cuenta, buscar por id. La infraestructura es PostgreSQL hoy, otro adapter mañana. La interfaz es HTTP, DTOs, validación de entrada.
NestJS vive abajo en interfaz e infra. Si ves @Injectable o TypeORM dentro de domain/, algo se rompió.
Un microservicio es otro binario con otro ciclo de release. Un monolito modular es el mismo proceso con paredes internas. Cuando Transfers llegue, quiero que sea un módulo que respete esas paredes — no un copy-paste del controller de Arkano.
Hoy me importa el ACID local, el stack trace que no salta entre pods, y poder testear Account.suspend() sin levantar Docker. Mañana — si el producto y el equipo lo piden — importará desplegar Transfers sin tocar Accounts.
| Hoy (Fase 0–1) | Cuando tenga sentido separar |
|---|---|
| Un deploy, transacciones locales | Dos equipos que no pueden coordinar releases |
| Llamadas in-process | Un contexto que escala 10× y otro no |
| Un backlog, pocas personas | Fronteras de datos claras, sin joins mágicos entre servicios |
Si mueves dinero entre bases de datos sin modelo de consistencia, vuelves a la pregunta del timeout del post anterior — solo que ahora el fallo es distribuido. No estoy ahí todavía. Estoy intentando que cuentas esté bien modeladas antes de discutir pods.
La Fase 0 no es un slide. Ya está en GitHub con esta forma:
apps/accounts-service/
├── accounts/
│ ├── domain/ # sin @nestjs, sin typeorm
│ ├── application/
│ ├── infrastructure/
│ └── interface/
libs/domain-common/
La entidad Account vive en dominio y defiende reglas, no es un DTO con getters:
export class Account {
private constructor(
readonly id: AccountId,
readonly ownerName: string,
private _status: AccountStatus,
readonly createdAt: Date,
) {}
static open(id: AccountId, ownerName: string): Account {
return new Account(id, ownerName, AccountStatus.ACTIVE, new Date());
}
suspend(): void {
if (this._status === AccountStatus.CLOSED) {
throw new AccountClosedError(this.id);
}
this._status = AccountStatus.SUSPENDED;
}
}
El use case habla con un puerto, no con TypeORM:
export class CreateAccountUseCase {
constructor(private readonly repo: AccountRepositoryPort) {}
async execute(command: CreateAccountCommand): Promise<AccountId> {
const id = AccountId.generate();
const account = Account.open(id, command.ownerName);
await this.repo.save(account);
return id;
}
}
Y el controller — como prometí cuando hablamos del POST /transfer — solo pasa datos y devuelve respuesta:
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() dto: CreateAccountDto): Promise<AccountResponseDto> {
const id = await this.createAccount.execute({
ownerName: dto.ownerName,
});
return AccountResponseDto.fromId(id);
}
pnpm test corre tests de dominio y application sin base de datos. Swagger en /api/docs. Docker Compose levanta PostgreSQL en el puerto 5433. Nada de Kafka todavía. Nada de fraude. Bien.
"¿Cuándo partirías Accounts y Transfers?"
Yo no arranco con Kubernetes. Miro si hay dos equipos que necesitan publicar sin esperarse, si un contexto pide mucha más escala que otro, si ya tienes trazas y contratos de eventos que amorticen el dolor distribuido. Si no hay dos señales claras, me quedo con módulos fuertes dentro del mismo deploy.
Los ports que escribes hoy ayudan mañana: el use case no debería enterarse si el adaptador es TypeORM o un mensaje en un bus.
Transferencias y ledger vienen en las siguientes fases en el mismo binario hasta que el dominio diga lo contrario. El próximo artículo es sobre dinero en código: centavos, bigint, y por qué el float me da mala espina en un core bancario.
En el post anterior dibujé las capas detrás de un POST /transfer. Aquí elegí dónde pongo el código mientras aprendo: un edificio, departamentos ordenados, un solo deploy por ahora.
Serie: makingcode.dev/series/fintech-core-platform
Anterior: Detrás de un POST /transfer
Código: github.com/AndresED/fintech-core-platform