Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

40 Commits

Folders and files

Repository files navigation

koa-demo — Prueba Técnica Java / Spring Boot

Microservicio de gestión de pagos construido con Spring Boot 4 y PostgreSQL. Responde a los requerimientos de la prueba técnica.

Instancia AWS [inactive] — disponible de forma temporal para evaluacion.

Recurso URL / Valor
Health xxx/health
Pagos CR141 (PAID, 3 pagos) xxx/payments/CR141
Pagos CR001 (ACTIVE, parciales) xxx/payments/CR001
API Key demo abcdefghijklmnopqrstuvwxyz0123456789abcdefgh
Repositorio https://github.com/Daespinosag/kata-koa-api

Tabla de contenido

  1. Prerequisitos
  2. Puesta en marcha
  3. Configuración de Postman
  4. API
  5. Seguridad — API Key
  6. Base de datos y SQL
  7. Arquitectura
  8. Tests
  9. CI/CD — Deploy a EC2
  10. Caso de producción
  11. Uso de herramientas de IA

1. Prerequisitos

Herramienta Versión mínima Notas
Java 17 Solo para desarrollo local (Opción B)
Maven incluido (./mvnw) Wrapper incluido en el repo
Docker 24+ Requerido para Opción A
Docker Compose v2 (plugin) docker compose (sin guión)
Postman cualquier versión Para pruebas manuales

No se necesita instalar Postgres — Docker lo levanta automáticamente.


2. Puesta en marcha

Opción A — Docker (recomendado)

git clone https://github.com/Daespinosag/kata-koa-api.git
cd kata-koa-api/koa-demo

docker compose up

Primer arranque: descarga imágenes y compila la app (~3 min). Los siguientes arranques usan caché.

Cuando aparezca Started KoaDemoApplication, la app está lista:

curl http://localhost:8080/health
# → OK

API key para pruebas (incluida como default en compose.yaml):

dev:abcdefghijklmnopqrstuvwxyz0123456789abcdefgh

La clave es abcdefghijklmnopqrstuvwxyz0123456789abcdefgh (el prefijo dev: es el clientId).

curl -H "X-API-Key: abcdefghijklmnopqrstuvwxyz0123456789abcdefgh" \
     http://localhost:8080/payments/CR001

Los seeds de desarrollo (200 créditos, 298 pagos) se aplican automáticamente via Flyway.


Opción B — Desarrollo local con recarga

Requiere Docker para la base de datos; Spring Boot lo levanta automáticamente. Asume que ya clonaste el repo.

cd kata-koa-api/koa-demo

export API_KEYS='dev:abcdefghijklmnopqrstuvwxyz0123456789abcdefgh'
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev

Spring Boot detecta compose-dev.yaml (solo Postgres, sin el contenedor de la app) y lo levanta solo.

Hot reload: Spring DevTools está activo en el perfil dev. Al compilar con ./mvnw compile o desde el IDE (IntelliJ: Build → Build Project, VS Code: guardar), la app se reinicia en ~2 s sin detener Postgres.


3. Configuración de Postman

El repositorio incluye una colección y un entorno listos para importar:

Archivo Ruta
Colección .postman/koa-demo.postman_collection.json
Entorno .postman/koa-demo.postman_environment.json

Pasos:

  1. Abrir Postman → Import
  2. Importar ambos archivos
  3. Seleccionar el entorno koa-demo en el selector superior derecho
  4. Ajustar las variables según la tabla de abajo

Variables del entorno:

Variable Valor por defecto Descripción Cómo obtenerla
baseUrl http://localhost:8080 URL base de la API. Cambiar a http://3.144.197.59:8080 para apuntar a la instancia AWS URL del servidor donde corre la app
apiKey abcdefghijklmnopqrstuvwxyz0123456789abcdefgh Clave de autenticación para el header X-API-Key En local: valor de API_KEYS exportado antes de arrancar. En Docker: default en compose.yaml línea API_KEYS. En AWS: la clave demo ya está activa
creditId CR001 Número de crédito usado en las peticiones GET /payments/{creditId} Cualquier crédito de los seeds: CR001–CR140 (ACTIVE), CR141–CR180 (PAID), CR181–CR200 (CANCELLED)

Todos los endpoints están documentados en la colección con ejemplos de request, response y casos de error.


4. API

Método Ruta Auth Descripción
POST /payments API Key Registrar un pago
GET /payments/{numeroCredito} API Key Listar pagos de un crédito
GET /health No Health check

POST /payments

{
  "numeroCredito": "CR001",
  "valor": 500000,
  "fecha": "2026-02-18"
}

Respuestas:

Código Causa
201 Created Pago registrado
409 Conflict Pago duplicado (numeroCredito + fecha + valor)
400 Bad Request Campo inválido o faltante
404 Not Found Crédito no existe
422 Unprocessable Entity Regla de negocio violada (crédito cancelado, sobrepago, etc.)
401 Unauthorized API Key ausente o inválida

GET /payments/{numeroCredito}

Retorna pagos ordenados por fecha ascendente. Soporta paginación por cursor (?after=<cursor>&limit=<n>).

{
  "numeroCredito": "CR001",
  "items": [
    {
      "paymentId": "...",
      "numeroCredito": "CR001",
      "valor": 500000.0,
      "fecha": "2026-02-18",
      "paidAt": "2026-02-18T17:00:00Z",
      "registeredAt": "2026-04-11T..."
    }
  ],
  "limit": 50,
  "nextCursor": null
}

Los errores siguen el estándar RFC 9457 Problem Details. Ver docs/spec/09-error-handling.md.


5. Seguridad — API Key

  • Header requerido: X-API-Key
  • Las claves se configuran mediante la variable de entorno API_KEYS (formato: clientId:clave, múltiples separadas por |)
  • No hay secretos hardcodeados en el código
  • /health está excluido de autenticación
# Ejemplo con clave demo
curl -H "X-API-Key: abcdefghijklmnopqrstuvwxyz0123456789abcdefgh" \
     http://localhost:8080/payments/CR001

# Sin clave → 401
curl http://localhost:8080/payments/CR001

Rate limiting: 30 req/min por IP (pre-auth) y 100 req/min por cliente autenticado.

Ver docs/spec/08-security-api-key.md para detalles.


6. Base de datos y SQL

Schema

El schema completo está en koa-demo/src/main/resources/db/schema.sql. Flyway lo aplica como migración V1__schema.sql.

Tablas:

  • credits — crédito otorgado (aggregate root). Estados: ACTIVE, PAID, CANCELLED
  • payments — pagos registrados contra un crédito. Append-only

Idempotencia: restricción UNIQUE (credit_id, payment_date, amount) — un pago duplicado retorna 409 sin insertar un registro nuevo.

Consultas requeridas

Total pagado por numero_credito:

SELECT credit_id,
       SUM(amount) AS total_pagado
FROM   payments
WHERE  credit_id = :creditId
GROUP  BY credit_id;

Top 5 créditos con mayor pago en un rango de fechas:

Implementado en PostgresPaymentRepository#topCreditsByPaidAtSumInRange.

SELECT credit_id,
       SUM(amount) AS total
FROM   payments
WHERE  paid_at >= :from
  AND  paid_at <  :to
GROUP  BY credit_id
ORDER  BY total DESC
LIMIT  5;

Índices y justificación

Índice Columnas Propósito
idx_payments_credit_paid_at_id (credit_id, paid_at, payment_id) INCLUDE (amount, currency, payment_date, ...) GET /payments/{id} con paginación keyset — index-only scan
idx_payments_paid_at_credit_amount (paid_at) INCLUDE (credit_id, amount) Agregación por rango de fechas para top-N

Sin índice en paid_at la consulta top-N haría sequential scan sobre toda la tabla. Ver análisis completo en docs/spec/07-persistence-and-schema.md.


7. Arquitectura

El proyecto sigue arquitectura hexagonal (ports & adapters) dentro de un único módulo Maven.

┌─────────────────────────────────────────────────────────────┐
│                        HTTP Client                          │
└────────────────────────────┬────────────────────────────────┘
                             │ X-API-Key
                             ▼
┌─────────────────────────────────────────────────────────────┐
│  Infrastructure — Entrada                                   │
│  ┌──────────────────┐  ┌────────────────────────────────┐   │
│  │ ApiKeyAuth Filter│  │ IpRateLimit / ClientRateLimit  │   │
│  └────────┬─────────┘  └───────────────┬────────────────┘   │
│           │                            │                     │
│  ┌────────▼────────────────────────────▼────────────────┐   │
│  │  Controllers  (REST)                                 │   │
│  │  POST /payments  │  GET /payments/{id}  │  GET /health│   │
│  └────────┬─────────────────────┬───────────────────────┘   │
└───────────│─────────────────────│─────────────────────────── ┘
            │                     │
┌───────────▼─────────────────────▼─────────────────────────── ┐
│  Application (casos de uso)                                   │
│  RegisterPaymentCommandHandler   FindPaymentsByCreditHandler  │
│           │                              │                    │
│  ┌────────▼──────────────────────────────▼────────────────┐   │
│  │  Domain  (reglas de negocio)                          │   │
│  │  Credit (FSM) · Payment · Money (COP) · PaidAt        │   │
│  └────────┬──────────────────────────────┬───────────────┘   │
└───────────│──────────────────────────────│───────────────────┘
            │  puerto (interface)           │
┌───────────▼──────────────────────────────▼───────────────────┐
│  Infrastructure — Salida                                      │
│  PostgresCreditRepository  │  PostgresPaymentRepository       │
│              (JDBC — sin JPA)                                 │
│                       │                                       │
│               ┌───────▼────────┐                             │
│               │   PostgreSQL   │                             │
│               │ credits        │                             │
│               │ payments       │                             │
│               │ UNIQUE idempot.│                             │
│               └────────────────┘                             │
└───────────────────────────────────────────────────────────────┘

Estructura de paquetes:

payments/
  credit/
    domain/          ← Aggregate Credit, reglas de negocio, puertos
    infrastructure/
      persistence/   ← PostgresCreditRepository (JDBC)
  payment/
    domain/          ← Aggregate Payment, value objects
    application/
      register/      ← RegisterPaymentCommandHandler
      find_by_credit/← FindPaymentsByCreditQueryHandler
    infrastructure/
      persistence/   ← PostgresPaymentRepository (JDBC)
      rest/          ← Controllers (entrada HTTP)
shared/
  domain/            ← Money, PaidAt, Clock, EventBus
  infrastructure/
    security/        ← ApiKeyAuthenticationFilter, rate limiting
    errors/          ← GlobalExceptionHandler, Problem Details

Decisiones clave:

  • JDBC sin JPA — control explícito del SQL, sin lazy loading inesperado (ADR-0002)
  • Money como value object COP-only — evita errores de divisa (ADR-0003)
  • Idempotencia por constraint DB, no por lógica de aplicación (ADR-0007)
  • Errores en RFC 9457 Problem Details (ADR-0005)

8. Tests

# Todos los tests (desde koa-demo/)
./mvnw test

# Solo tests de integración (requieren Docker para Testcontainers)
./mvnw test -Dgroups="integration"

# Excluir integración (solo unitarios)
./mvnw test -DexcludedGroups="integration"

Los tests de integración usan Testcontainers — levantan Postgres automáticamente, sin configuración adicional.

Cobertura: 21 clases de test que cubren dominio, aplicación, persistencia, API (MockMvc + REST Docs) y arquitectura hexagonal (ArchUnit).


9. CI/CD — Deploy a EC2

La aplicación está desplegada en AWS EC2 (us-east-2) y accesible en:

http://3.144.197.59:8080

La instancia estara disponible de forma temporal para evaluacion.

El pipeline se dispara automáticamente con un tag v*:

git tag v1.0.0
git push origin v1.0.0

Flujo:

  1. Tests contra Postgres real (GitHub Actions service container)
  2. Build imagen Docker
  3. SCP imagen al EC2
  4. docker compose up en el servidor
  5. Healthcheck con reintentos

Ver .github/workflows/deploy.yml.

Secrets requeridos en GitHub → Settings → Secrets:

Secret Valor
EC2_HOST IP pública del EC2
EC2_SSH_KEY Contenido del .pem

10. Caso de producción

10.1 Errores 500 intermitentes — ¿qué revisaría primero?

  1. Logs estructurados (/actuator/logs o Loki/CloudWatch): buscar el traceId del error 500 y reconstruir el stack completo sin exponer al cliente.
  2. Pool de conexiones (/actuator/metrics/hikaricp.connections.active): si el pool se agota bajo carga, las peticiones siguientes fallan. Solución: ajustar maximum-pool-size o revisar transacciones largas que no liberan.
  3. Timeouts de BD: un query lento (>30 s) puede disparar un SocketTimeoutException que el GlobalExceptionHandler no mapea a 400/409 → cae a 500. Revisar statement-timeout en Postgres.
  4. Optimistic lock contention (OptimisticLockException): dos peticiones concurrentes al mismo crédito. El handler retorna 409, pero si el retry está mal configurado puede escalar.
  5. OOM / GC pause: revisar métricas de heap (/actuator/metrics/jvm.memory.used). Con -XX:MaxRAMPercentage=75 en el contenedor es poco probable, pero un leak en caché de rate-limit podría manifestarse.

10.2 Base de datos lenta — pasos técnicos

  1. EXPLAIN ANALYZE sobre las queries reportadas como lentas. Los índices idx_payments_credit_paid_at_id e idx_payments_paid_at_credit_amount cubren los accesos más frecuentes; verificar que el planner los use.
  2. Autovacuum: tabla payments es append-only pero el índice de idempotencia se actualiza en cada INSERT. Revisar pg_stat_user_tables — si n_dead_tup crece, autovacuum puede estar rezagado.
  3. Connection pool: pg_stat_activity muestra conexiones activas. Si hay muchas en idle in transaction, hay transacciones que no se cierran correctamente.
  4. Paginación: reemplazar OFFSET por keyset pagination (ya implementado con cursor en GET /payments/{id}) para evitar full-scan en páginas profundas.
  5. Particionado por paid_at: a futuro, si la tabla supera decenas de millones de filas, particionar por mes permite partition pruning en las queries de rango.

10.3 Métricas a monitorear en un sistema financiero

Métrica Por qué
http.server.requests (p99 por endpoint) Latencia percibida por el cliente
payments.registered.count vs payments.duplicate.count Ratio de idempotencia — spike de duplicados indica reintento masivo del cliente
hikaricp.connections.pending Presión sobre el pool de BD
jvm.memory.used / jvm.gc.pause Salud de la JVM
process.cpu.usage Detección de CPU-bound bajo carga
Tasa de errores 5xx / 4xx SLA contractual; alerta si 5xx > 0.1%
db.statement p99 (Micrometer JDBC) Queries lentas antes de que impacten al usuario

Exportados via /actuator/prometheus → Prometheus + Grafana. Ver docs/spec/10-observability.md.


11. Uso de herramientas de IA

Herramientas utilizadas

Herramienta Rol en el proyecto
Claude Code (Anthropic) Orquestador principal — arquitectura, implementación, infraestructura
Cursor Editor con asistencia contextual para refactoring fino y revisión de código

Flujo multi-agente (SSD)

El desarrollo siguió un flujo de múltiples agentes especializados coordinados por Claude Code:

  Developer
     │
     ▼
┌─────────────────────────────────────────────┐
│              Claude Code (orquestador)       │
│                                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │koa-arch  │  │koa-coder │  │koa-tester│  │
│  │Diseño    │→ │Impl.     │→ │MockMvc + │  │
│  │hexagonal │  │JDBC/API  │  │REST Docs │  │
│  └──────────┘  └──────────┘  └──────────┘  │
│  ┌──────────┐  ┌──────────┐                 │
│  │koa-sec   │  │koa-review│                 │
│  │API Key + │  │Auditoría │                 │
│  │rate limit│  │calidad   │                 │
│  └──────────┘  └──────────┘                 │
└─────────────────────────────────────────────┘
     │
     ▼ código generado
  Cursor
     │  revisión + ajustes finos
     ▼
  Developer valida
     │  ./mvnw test + Postman
     ▼
  git commit → CI/CD

Etapas del flujo:

  1. Especificación — Claude Code + koa-architect: análisis de la prueba técnica, definición de bounded contexts, ADRs y decisiones de diseño (docs/spec/)
  2. Implementación — koa-coder: generación de domain objects, use cases, controllers, JDBC repositories y configuraciones Spring
  3. Testing — koa-tester: tests unitarios (dominio), integración (Testcontainers), MockMvc + REST Docs snippets, ArchUnit
  4. Seguridad — koa-security: ApiKeyAuthenticationFilter, rate limiting Bucket4j, Problem Details RFC 9457
  5. Revisión — Cursor: revisión contextual del diff, refactoring fino, naming consistency
  6. Infraestructura — Claude Code: Dockerfile, compose.yaml, GitHub Actions CI/CD, deploy EC2

Validación del código generado:

  • Revisión manual de cada archivo antes del commit
  • Tests de integración contra Postgres real (no mocks): PaymentsApiIT, TopCreditsByPaidAtRangeRepositoryIT
  • HexagonalArchitectureTest (ArchUnit) — las capas no se violan aunque el código sea generado
  • Pipeline CI bloquea el deploy si algún test falla
  • Casos edge verificados manualmente con Postman: idempotencia, crédito cancelado, sobrepago, rate limiting

La IA elimina fricción en la escritura mecánica. El diseño, las decisiones de arquitectura y la validación fueron responsabilidad del desarrollador.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages