Skip to content

edgarrth/java-postgres-multitypes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PostgreSQL Multitypes

PoC que muestra el uso de los diferentes tipos de datos que se pueden usar en postgres:

Capacidad PostgreSQL Tabla principal dedicada Caso de uso
tsvector payment_search_documents Búsqueda full-text de pagos, conciliaciones, canales y descripciones operativas.
pgvector payment_semantic_rules Búsqueda semántica de reglas de enrutamiento o decisión de pagos.
PostgreSQL como cache payment_cache_entries Cache temporal de decisiones de riesgo, tasas o datos de comercios.
jsonb payment_profiles Perfil flexible de comercio/cliente con atributos variables.
Eventos / Outbox + LISTEN/NOTIFY payment_outbox_events Persistencia transaccional de eventos de dominio y notificación asíncrona con PostgreSQL.
bytea payment_digital_certificates Almacenamiento de certificados o llaves públicas PEM de procesadores de pago.

Stack

  • Java 25
  • Spring Boot 4.1.0
  • JDBC + JdbcTemplate, porque permite usar capacidades nativas de PostgreSQL (tsvector, vector, jsonb, bytea, LISTEN/NOTIFY) sin forzar un ORM donde no aporta valor en la PoC.
  • Flyway como único mecanismo de schema + seed data. No hay scripts duplicados de inicialización en Docker.
  • Eventos con patrón híbrido Outbox + LISTEN/NOTIFY: el evento queda persistido en Outbox para durabilidad y NOTIFY despierta al microservicio cuando hay un nuevo evento o cambio de estado.

Estructura del proyecto

axiz-payment-processing-poc/
├── README.md
├── VALIDATION.md
├── pom.xml
├── datasets/
│   ├── flyway/
│   │   ├── V1__create_payment_processing_schema.sql
│   │   ├── V2__seed_payment_processing_data.sql
│   │   └── V3__add_payment_event_listen_notify.sql
│   ├── sample-payment-certificate.pem
│   └── sample-payment-profile.json
├── infraestructura/
│   ├── docker-compose.yml
│   └── http/
│       └── payment-processing-api.http
├── scripts/
│   └── maven-with-docker.sh
└── src/
    ├── main/
    │   ├── java/pe/axiz/paymentprocessing/
    │   │   ├── PaymentProcessingApplication.java
    │   │   ├── domain/
    │   │   │   ├── model/
    │   │   │   ├── port/in/
    │   │   │   ├── port/out/
    │   │   │   └── exception/
    │   │   ├── application/service/
    │   │   └── infrastructure/adapter/
    │   │       ├── in/rest/
    │   │       └── out/
    │   │           ├── messaging/
    │   │           └── persistence/
    │   └── resources/application.yml
    └── test/java/pe/axiz/paymentprocessing/application/service/

Diagrama de arquitectura

flowchart LR
    Client[Cliente REST / HTTP file] --> Rest[REST Controllers]
    Rest --> InPorts[Puertos de entrada / Use cases]
    InPorts --> App[Servicios de aplicación]
    App --> Domain[Modelo de dominio]
    App --> OutPorts[Puertos de salida]
    OutPorts --> Jdbc[Adaptadores JDBC]
    Jdbc --> Pg[(PostgreSQL + pgvector)]

    Pg --> Trigger[Trigger PostgreSQL]
    Trigger --> Notify[pg_notify payment_events]
    Notify --> Listener[Adapter LISTEN/NOTIFY Java]
    Listener --> NotificationTable[(payment_event_notifications)]

    subgraph Hexagonal Architecture
        Rest
        InPorts
        App
        Domain
        OutPorts
        Jdbc
        Listener
    end

    subgraph PostgreSQL capabilities
        Pg --> Tsvector[tsvector]
        Pg --> Vector[pgvector]
        Pg --> Cache[unlogged cache table]
        Pg --> Jsonb[jsonb]
        Pg --> Events[outbox events]
        Pg --> Bytea[bytea PEM]
    end
Loading

Flujo de eventos con Outbox + LISTEN/NOTIFY

sequenceDiagram
    participant C as Cliente REST
    participant API as PaymentEventController
    participant UC as PaymentEventService
    participant DB as PostgreSQL
    participant TR as Trigger DB
    participant L as PostgresPaymentEventNotificationListener

    C->>API: POST /api/v1/payment-events
    API->>UC: appendEvent(...)
    UC->>DB: insert payment_outbox_events(status=PENDING)
    DB->>TR: after insert
    TR->>DB: pg_notify('payment_events', {eventId, action})
    DB-->>L: NOTIFY payment_events
    L->>DB: insert payment_event_notifications
    C->>API: GET /api/v1/payment-events/notifications
    API-->>C: Notificaciones recibidas por listener
Loading

Estructura DDD + Hexagonal

Dominio

Ubicación: src/main/java/pe/axiz/paymentprocessing/domain

Contiene el lenguaje del negocio y no depende de Spring:

  • SearchDocument: documento operacional de pagos indexado con tsvector.
  • SemanticRule: regla de ruteo/decisión con embedding en pgvector.
  • CacheEntry: entrada temporal de cache basada en PostgreSQL.
  • PaymentProfile: perfil flexible de comercio/cliente usando jsonb.
  • PaymentOutboxEvent: evento de dominio persistido como outbox.
  • PaymentEventNotification: notificación recibida desde PostgreSQL LISTEN/NOTIFY.
  • DigitalCertificate: certificado/llave pública PEM almacenado como bytea.

Servicios de aplicación

Ubicación: application/service

Implementan los casos de uso, validaciones de aplicación y coordinación con los puertos de salida:

  • PaymentSearchService
  • SemanticRoutingService
  • PaymentCacheService
  • PaymentProfileService
  • PaymentEventService
  • DigitalCertificateService
  • DeterministicEmbeddingService

DeterministicEmbeddingService crea embeddings determinísticos de 6 dimensiones con SHA-256. En producción, este componente se reemplazaría por un modelo real o servicio de embeddings, manteniendo intacto el puerto/repositorio.

Adaptadores de salida PostgreSQL

Ubicación: infrastructure/adapter/out/persistence

Implementan los repositorios con JdbcTemplate y SQL nativo para aprovechar funcionalidades específicas de PostgreSQL.

Adaptador de mensajería PostgreSQL

Ubicación: infrastructure/adapter/out/messaging

PostgresPaymentEventNotificationListener mantiene una conexión dedicada a PostgreSQL y ejecuta:

LISTEN payment_events;

Cuando recibe un NOTIFY, parsea el payload mínimo {eventId, action}, consulta el evento durable en payment_outbox_events y guarda la evidencia completa en payment_event_notifications.

Infraestructura mínima

La carpeta infraestructura contiene solo lo necesario para probar el concepto:

infraestructura/
├── docker-compose.yml
└── http/payment-processing-api.http

La precarga se ejecuta automáticamente con Flyway al iniciar el microservicio:

  • V1__create_payment_processing_schema.sql: crea extensión vector, tablas e índices base.
  • V2__seed_payment_processing_data.sql: inserta datos demo para las seis funcionalidades.
  • V3__add_payment_event_listen_notify.sql: crea tabla de auditoría de notificaciones, función PL/pgSQL, trigger y pg_notify('payment_events', ...).

En pom.xml, la carpeta datasets/flyway se copia al classpath como db/migration, por eso Spring Boot/Flyway la detecta sin duplicar scripts.

Cómo levantar la base de datos

Desde la raíz del proyecto:

cd infraestructura
docker compose up -d

Conectarse manualmente:

docker exec -it axiz-payment-postgres psql -U axiz -d axiz_payments

Cómo compilar

Con Maven local y JDK 25:

mvn clean verify

Luego ejecutar la aplicación:

mvn spring-boot:run

O con Docker para Maven/JDK:

./scripts/maven-with-docker.sh spring-boot:run

Variables soportadas:

Variable Default
DB_URL jdbc:postgresql://localhost:5432/axiz_payments
DB_USERNAME axiz
DB_PASSWORD axiz_secret
SERVER_PORT 8080
PAYMENT_EVENTS_LISTEN_NOTIFY_ENABLED true
PAYMENT_EVENTS_LISTEN_NOTIFY_CHANNEL payment_events
PAYMENT_EVENTS_LISTEN_NOTIFY_POLL_TIMEOUT_MILLIS 1000
PAYMENT_EVENTS_LISTEN_NOTIFY_RECONNECT_DELAY_MILLIS 2000

Endpoints en orden de ejecución recomendado

Orden Capacidad Método Endpoint Descripción funcional Descripción técnica
1 tsvector POST /api/v1/payment-search-documents Registra un documento operacional de pago para búsqueda. Inserta en payment_search_documents; la columna search_vector se calcula automáticamente como tsvector.
2 tsvector GET /api/v1/payment-search-documents?query=pago qr comercio Busca pagos o conciliaciones por texto. Usa websearch_to_tsquery('spanish', ?) y ranking con ts_rank_cd.
3 pgvector POST /api/v1/payment-semantic-rules Registra una regla de ruteo semántico. Genera embedding determinístico de 6 dimensiones e inserta en columna vector(6).
4 pgvector GET /api/v1/payment-semantic-rules/nearest?text=pagos tarjeta&limit=3 Encuentra reglas parecidas al texto enviado. Usa operador de distancia coseno <=> sobre pgvector.
5 Cache PUT /api/v1/payment-cache/{cacheKey} Guarda una decisión temporal de riesgo/tasa/comercio. Hace upsert en tabla UNLOGGED payment_cache_entries con TTL.
6 Cache GET /api/v1/payment-cache/{cacheKey} Consulta una entrada temporal. Lee por PK y valida expiración por expires_at.
7 Cache DELETE /api/v1/payment-cache/expired Elimina entradas vencidas. Ejecuta limpieza física de registros expirados.
8 jsonb POST /api/v1/payment-profiles Registra perfil flexible de comercio/cliente. Inserta attributes como jsonb.
9 jsonb GET /api/v1/payment-profiles?attributeName=riskLevel&value=LOW Busca perfiles por atributo variable. Consulta attributes ->> ? = ? e índice GIN para consultas JSONB.
10 Eventos POST /api/v1/payment-events Registra un evento de dominio de pagos. Inserta en payment_outbox_events; el trigger ejecuta pg_notify('payment_events', '{eventId, action}').
11 Eventos GET /api/v1/payment-events?status=PENDING Lista eventos pendientes de publicación. Filtra por estado e índice (status, created_at).
12 Eventos GET /api/v1/payment-events/notifications?limit=20 Lista notificaciones PostgreSQL recibidas por el listener Java. Consulta payment_event_notifications, tabla alimentada por PostgresPaymentEventNotificationListener.
13 Eventos PATCH /api/v1/payment-events/{id}/published Marca un evento como publicado. Actualiza status='PUBLISHED', published_at=now() y vuelve a disparar NOTIFY por cambio de estado.
14 bytea POST /api/v1/payment-certificates Guarda certificado/llave PEM de procesador. Decodifica Base64 y persiste bytes en bytea; calcula fingerprint SHA-256.
15 bytea GET /api/v1/payment-certificates Lista metadatos de certificados. Retorna alias, owner, algoritmo, fingerprint y tamaño.
16 bytea GET /api/v1/payment-certificates/{alias}?includePem=true Consulta certificado por alias. Lee bytea y opcionalmente retorna PEM como texto.

Request/Response rápido

También puedes usar infraestructura/http/payment-processing-api.http desde IntelliJ IDEA, VS Code REST Client o cualquier cliente compatible.

1. Insertar documento tsvector

Request:

POST /api/v1/payment-search-documents
Content-Type: application/json

{
  "paymentReference": "PAY-QR-9001",
  "channel": "QR",
  "description": "Pago QR aprobado para comercio veterinaria con liquidación inmediata"
}

Response esperado:

{
  "id": "uuid",
  "paymentReference": "PAY-QR-9001",
  "channel": "QR",
  "description": "Pago QR aprobado para comercio veterinaria con liquidación inmediata",
  "createdAt": "2026-07-09T..."
}

2. Buscar con tsvector

GET /api/v1/payment-search-documents?query=pago qr comercio

Resultado: lista de documentos más relevantes según ranking full-text.

3. Insertar regla pgvector

POST /api/v1/payment-semantic-rules
Content-Type: application/json

{
  "ruleCode": "ROUTE-CARD-LOW-RISK",
  "title": "Ruta tarjeta bajo riesgo",
  "description": "Procesar pagos con tarjeta de bajo monto usando adquirente principal y autorización inmediata"
}

Resultado: regla creada con embedding determinístico.

4. Buscar reglas cercanas con pgvector

GET /api/v1/payment-semantic-rules/nearest?text=pagos con tarjeta de monto bajo&limit=3

Resultado: lista ordenada por menor distancia semántica.

5. Insertar cache

PUT /api/v1/payment-cache/merchant-risk:MRC-2001
Content-Type: application/json

{
  "ttlSeconds": 3600,
  "value": {
    "merchantId": "MRC-2001",
    "riskLevel": "LOW",
    "decision": "ALLOW"
  }
}

6. Consultar cache

GET /api/v1/payment-cache/merchant-risk:MRC-2001

7. Insertar perfil jsonb

POST /api/v1/payment-profiles
Content-Type: application/json

{
  "profileCode": "PROFILE-MERCHANT-NEW",
  "documentNumber": "20445566778",
  "attributes": {
    "customerSegment": "SME",
    "merchantCategory": "retail",
    "riskLevel": "MEDIUM",
    "settlementMode": "T1"
  }
}

8. Consultar perfil jsonb

GET /api/v1/payment-profiles?attributeName=riskLevel&value=LOW

9. Crear evento Outbox y disparar NOTIFY

POST /api/v1/payment-events
Content-Type: application/json

{
  "aggregateId": "PAY-QR-9001",
  "eventType": "PaymentAuthorized",
  "payload": {
    "paymentReference": "PAY-QR-9001",
    "amount": 150.90,
    "currency": "PEN",
    "channel": "QR"
  }
}

Qué ocurre internamente:

  1. El endpoint inserta el evento en payment_outbox_events con estado PENDING.
  2. El trigger trg_notify_payment_outbox_event ejecuta pg_notify('payment_events', '{eventId, action}'). No envía todo el evento para evitar el límite de tamaño de NOTIFY.
  3. PostgresPaymentEventNotificationListener recibe el NOTIFY mediante LISTEN payment_events, consulta el evento por eventId en Outbox y reconstruye la notificación completa.
  4. El listener guarda la evidencia en payment_event_notifications.

10. Consultar eventos Outbox

GET /api/v1/payment-events?status=PENDING

11. Consultar notificaciones recibidas por LISTEN/NOTIFY

GET /api/v1/payment-events/notifications?limit=20

Response esperado:

[
  {
    "id": "uuid",
    "eventId": "uuid-del-evento-outbox",
    "channel": "payment_events",
    "action": "EVENT_APPENDED",
    "aggregateId": "PAY-QR-9001",
    "eventType": "PaymentAuthorized",
    "status": "PENDING",
    "payload": {
      "paymentReference": "PAY-QR-9001",
      "amount": 150.9,
      "currency": "PEN",
      "channel": "QR"
    },
    "receivedAt": "2026-07-09T..."
  }
]

12. Marcar evento como publicado y disparar otro NOTIFY

PATCH /api/v1/payment-events/{id}/published

El trigger envía otra notificación con:

{
  "action": "EVENT_STATUS_CHANGED",
  "status": "PUBLISHED"
}

13. Guardar certificado PEM en bytea

POST /api/v1/payment-certificates
Content-Type: application/json

{
  "alias": "processor-secondary-public-key",
  "owner": "Secondary Payment Processor",
  "algorithm": "RSA",
  "base64Pem": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHYybkRlbW9Pbmx5Rm9yQXhpelBvY05vdFJlYWxLZXkKOVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
}

Consultas SQL útiles

Revisar documentos full-text:

select payment_reference, channel, description, search_vector
from payment_search_documents;

Revisar embeddings:

select rule_code, embedding
from payment_semantic_rules;

Revisar cache:

select cache_key, cache_value, expires_at
from payment_cache_entries;

Revisar perfiles JSONB:

select profile_code, attributes
from payment_profiles
where attributes ->> 'riskLevel' = 'LOW';

Revisar eventos Outbox:

select id, aggregate_id, event_type, status, payload
from payment_outbox_events
order by created_at desc;

Revisar notificaciones recibidas por el listener:

select event_id, channel, action, aggregate_id, event_type, status, received_at
from payment_event_notifications
order by received_at desc;

Probar LISTEN/NOTIFY manualmente en psql:

LISTEN payment_events;

En otra sesión, insertar un evento:

insert into payment_outbox_events (id, aggregate_id, event_type, payload, status, created_at)
values ('66666666-6666-6666-6666-666666666661', 'PAY-MANUAL-0001', 'PaymentAuthorized', '{"amount":10,"currency":"PEN"}'::jsonb, 'PENDING', now())
on conflict (id) do nothing;

Revisar certificados:

select alias, owner, algorithm, fingerprint_sha256, octet_length(pem_content) as size_bytes
from payment_digital_certificates;

About

PoC de microservicio Java 25 con Spring Boot 4, demuestra capacidades avanzadas de PostgreSQL: tsvector, pgvector, cache, JSONB, LISTEN/NOTIFY, Outbox y BYTEA para certificados PEM.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors