Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
COMPOSE_PROJECT_NAME=payguard

ORACLE_IMAGE=gvenzl/oracle-free:23-slim-faststart
WALLET_ORACLE_PORT=1521
LOAN_ORACLE_PORT=1522
COLLATERAL_ORACLE_PORT=1523
WALLET_ORACLE_SYSTEM_PASSWORD=change-wallet-system-password
LOAN_ORACLE_SYSTEM_PASSWORD=change-loan-system-password
COLLATERAL_ORACLE_SYSTEM_PASSWORD=change-collateral-system-password
WALLET_ORACLE_APP_USER=payguard_wallet
LOAN_ORACLE_APP_USER=payguard_loan
COLLATERAL_ORACLE_APP_USER=payguard_collateral
WALLET_ORACLE_APP_PASSWORD=change-wallet-app-password
LOAN_ORACLE_APP_PASSWORD=change-loan-app-password
COLLATERAL_ORACLE_APP_PASSWORD=change-collateral-app-password
WALLET_ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1521/FREE
LOAN_ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1522/FREE
COLLATERAL_ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1523/FREE

WALLET_SERVICE_PORT=8081
LOAN_SERVICE_PORT=8082
COLLATERAL_SERVICE_PORT=8083

KAFKA_IMAGE=apache/kafka:latest
KAFKA_PORT=9092
KAFKA_CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
65 changes: 0 additions & 65 deletions Dockerfile

This file was deleted.

32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ It is not a payment-card switch or a full core-banking platform. The scope is in

| Service | Responsibility | Persistence | Current State |
|---|---|---|---|
| `wallet-service` | Account balances, double-entry ledger, holds/captures | PostgreSQL/Oracle via **jOOQ** (append-only, no ORM overhead) | In development |
| `loan-service` | Loan lifecycle, amortization schedules, repayments | Oracle via JPA/Hibernate | In development |
| `collateral-service` | Collateral locking, price valuation, LTV monitoring, liquidation | Oracle via JPA/Hibernate | In development |
| `wallet-service` | Account balances, double-entry ledger, holds/captures | Dedicated Oracle database via **jOOQ** (append-only, no ORM overhead) | In development |
| `loan-service` | Loan lifecycle, amortization schedules, repayments | Dedicated Oracle database via JPA/Hibernate | In development |
| `collateral-service` | Collateral locking, price valuation, LTV monitoring, liquidation | Dedicated Oracle database via JPA/Hibernate | In development |
| `api-gateway` | Single entry point; REST from clients, gRPC to internal services | — | Planned |

> `wallet-service` deliberately uses jOOQ instead of JPA: a ledger is append-only, and jOOQ gives explicit control over `INSERT`-only SQL with no risk of an accidental `UPDATE` slipping in through dirty-checking. `loan-service` and `collateral-service` manage mutable, CRUD-shaped state, where JPA is a better fit.
Expand Down Expand Up @@ -127,7 +127,7 @@ flowchart LR
### Dependency Rules

- Domain code has no Spring, persistence, or HTTP dependencies — invariants (e.g. debit = credit) are testable without a running application.
- Each service owns its own database schema; no service queries another's tables directly.
- Each service owns its own Oracle instance, credentials, volume, and schema; no service queries another's tables directly.
- Cross-service calls happen only through the declared client ports (`WalletServiceClient`, `LoanServiceClient`, `CollateralServiceClient`), each with a REST adapter today and a gRPC adapter available for the gateway path.
- Dependencies point inward, toward the domain core.

Expand Down Expand Up @@ -183,16 +183,26 @@ cd payguard
Create a `.env` file in the repository root:

```env
# Oracle
# Independent Oracle databases (copy `.env.example` to `.env` and set secrets)
ORACLE_IMAGE=gvenzl/oracle-free:23-slim-faststart
ORACLE_HOST_PORT=1521
ORACLE_PASSWORD=change-me-for-local-development

# Redis
REDIS_IMAGE=redis:7-alpine
REDIS_HOST_PORT=6379
WALLET_ORACLE_PORT=1521
LOAN_ORACLE_PORT=1522
COLLATERAL_ORACLE_PORT=1523
WALLET_ORACLE_SYSTEM_PASSWORD=change-wallet-system-password
LOAN_ORACLE_SYSTEM_PASSWORD=change-loan-system-password
COLLATERAL_ORACLE_SYSTEM_PASSWORD=change-collateral-system-password
WALLET_ORACLE_APP_USER=payguard_wallet
LOAN_ORACLE_APP_USER=payguard_loan
COLLATERAL_ORACLE_APP_USER=payguard_collateral
WALLET_ORACLE_APP_PASSWORD=change-wallet-app-password
LOAN_ORACLE_APP_PASSWORD=change-loan-app-password
COLLATERAL_ORACLE_APP_PASSWORD=change-collateral-app-password
WALLET_ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1521/FREE
LOAN_ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1522/FREE
COLLATERAL_ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1523/FREE

# Kafka (KRaft mode — no Zookeeper)
KAFKA_IMAGE=apache/kafka:latest
KAFKA_CLUSTER_ID=replace-with-a-generated-cluster-id

# Per-service ports
Expand Down
123 changes: 97 additions & 26 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
name: ${COMPOSE_PROJECT_NAME}

services:
oracle:
image: gvenzl/oracle-free:23-slim-faststart
container_name: payguard-oracle
oracle-wallet:
image: ${ORACLE_IMAGE}
container_name: ${COMPOSE_PROJECT_NAME}-oracle-wallet
ports:
- "${ORACLE_PORT}:1521"
- "${WALLET_ORACLE_PORT}:1521"
environment:
ORACLE_PASSWORD: ${ORACLE_PASSWORD}
APP_USER: ${ORACLE_APP_USER}
APP_PASSWORD: ${ORACLE_APP_PASSWORD}
ORACLE_PASSWORD: ${WALLET_ORACLE_SYSTEM_PASSWORD}
APP_USER: ${WALLET_ORACLE_APP_USER}
APP_PASSWORD: ${WALLET_ORACLE_APP_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "/opt/oracle/checkDBStatus.sh"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
retries: 20
start_period: 45s
volumes:
- oracle_data:/opt/oracle/oradata
- oracle_wallet_data:/opt/oracle/oradata

redis:
image: redis:7-alpine
container_name: payguard-redis
oracle-loan:
image: ${ORACLE_IMAGE}
container_name: ${COMPOSE_PROJECT_NAME}-oracle-loan
ports:
- "${REDIS_PORT}:6379"
- "${LOAN_ORACLE_PORT}:1521"
environment:
ORACLE_PASSWORD: ${LOAN_ORACLE_SYSTEM_PASSWORD}
APP_USER: ${LOAN_ORACLE_APP_USER}
APP_PASSWORD: ${LOAN_ORACLE_APP_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
test: ["CMD-SHELL", "/opt/oracle/checkDBStatus.sh"]
interval: 10s
timeout: 5s
retries: 20
start_period: 45s
volumes:
- redis_data:/data
- oracle_loan_data:/opt/oracle/oradata

oracle-collateral:
image: ${ORACLE_IMAGE}
container_name: ${COMPOSE_PROJECT_NAME}-oracle-collateral
ports:
- "${COLLATERAL_ORACLE_PORT}:1521"
environment:
ORACLE_PASSWORD: ${COLLATERAL_ORACLE_SYSTEM_PASSWORD}
APP_USER: ${COLLATERAL_ORACLE_APP_USER}
APP_PASSWORD: ${COLLATERAL_ORACLE_APP_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "/opt/oracle/checkDBStatus.sh"]
interval: 10s
timeout: 5s
retries: 20
start_period: 45s
volumes:
- oracle_collateral_data:/opt/oracle/oradata

kafka:
image: apache/kafka:latest
container_name: payguard-kafka
image: ${KAFKA_IMAGE}
container_name: ${COMPOSE_PROJECT_NAME}-kafka
ports:
- "${KAFKA_PORT}:9092"
environment:
Expand All @@ -57,12 +79,61 @@ services:
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:29092 --list"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
retries: 10
start_period: 20s
volumes:
- kafka_data:/tmp/kraft-combined-logs

wallet-service:
build:
context: .
dockerfile: payguard-wallet-service/Dockerfile
container_name: ${COMPOSE_PROJECT_NAME}-wallet-service
ports:
- "${WALLET_SERVICE_PORT}:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:oracle:thin:@oracle-wallet:1521/FREE
SPRING_DATASOURCE_USERNAME: ${WALLET_ORACLE_APP_USER}
SPRING_DATASOURCE_PASSWORD: ${WALLET_ORACLE_APP_PASSWORD}
KAFKA_BOOTSTRAP_SERVERS: kafka:29092
depends_on:
oracle-wallet: { condition: service_healthy }
kafka: { condition: service_healthy }

loan-service:
build:
context: .
dockerfile: payguard-loan-service/Dockerfile
container_name: ${COMPOSE_PROJECT_NAME}-loan-service
ports:
- "${LOAN_SERVICE_PORT}:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:oracle:thin:@oracle-loan:1521/FREE
SPRING_DATASOURCE_USERNAME: ${LOAN_ORACLE_APP_USER}
SPRING_DATASOURCE_PASSWORD: ${LOAN_ORACLE_APP_PASSWORD}
KAFKA_BOOTSTRAP_SERVERS: kafka:29092
depends_on:
oracle-loan: { condition: service_healthy }
kafka: { condition: service_healthy }

collateral-service:
build:
context: .
dockerfile: payguard-collateral-service/Dockerfile
container_name: ${COMPOSE_PROJECT_NAME}-collateral-service
ports:
- "${COLLATERAL_SERVICE_PORT}:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:oracle:thin:@oracle-collateral:1521/FREE
SPRING_DATASOURCE_USERNAME: ${COLLATERAL_ORACLE_APP_USER}
SPRING_DATASOURCE_PASSWORD: ${COLLATERAL_ORACLE_APP_PASSWORD}
KAFKA_BOOTSTRAP_SERVERS: kafka:29092
depends_on:
oracle-collateral: { condition: service_healthy }
kafka: { condition: service_healthy }

volumes:
oracle_data:
redis_data:
oracle_wallet_data:
oracle_loan_data:
oracle_collateral_data:
kafka_data:
13 changes: 13 additions & 0 deletions payguard-collateral-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# syntax=docker/dockerfile:1
FROM eclipse-temurin:25-jdk-alpine AS build
WORKDIR /workspace
COPY . .
RUN chmod +x mvnw && ./mvnw --batch-mode --no-transfer-progress -pl :payguard-collateral-service -am -DskipTests package

FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
RUN addgroup -S payguard && adduser -S payguard -G payguard
COPY --from=build /workspace/payguard-collateral-service/target/payguard-collateral-service-*.jar /app/app.jar
USER payguard
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "/app/app.jar"]
5 changes: 5 additions & 0 deletions payguard-collateral-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Collateral service

The service owns cash collateral locks, valuation history, LTV thresholds, margin calls, liquidation authorization, retention metadata, audit records, and outbox publication. Price and liquidation integrations are ports; the portfolio profile supplies a simulated foreign-currency oracle and execution adapter.

Build independently from the repository root with `docker build -f payguard-collateral-service/Dockerfile -t payguard/collateral-service .`. Run it with `SPRING_DATASOURCE_URL="$COLLATERAL_ORACLE_JDBC_URL"`, `SPRING_DATASOURCE_USERNAME="$COLLATERAL_ORACLE_APP_USER"`, and `SPRING_DATASOURCE_PASSWORD="$COLLATERAL_ORACLE_APP_PASSWORD"` from `.env`; its Flyway schema is applied only to the collateral-owned Oracle instance.
13 changes: 13 additions & 0 deletions payguard-loan-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# syntax=docker/dockerfile:1
FROM eclipse-temurin:25-jdk-alpine AS build
WORKDIR /workspace
COPY . .
RUN chmod +x mvnw && ./mvnw --batch-mode --no-transfer-progress -pl :payguard-loan-service -am -DskipTests package

FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
RUN addgroup -S payguard && adduser -S payguard -G payguard
COPY --from=build /workspace/payguard-loan-service/target/payguard-loan-service-*.jar /app/app.jar
USER payguard
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "/app/app.jar"]
5 changes: 5 additions & 0 deletions payguard-loan-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Loan service

The service owns the `Loan` aggregate, schedules, Actual/365 accrual, repayment waterfall, retention metadata, immutable audit records, outbox publication, and idempotent Kafka consumers. It references opaque borrower and collateral identifiers and requests wallet settlement through an event/port boundary.

Build independently from the repository root with `docker build -f payguard-loan-service/Dockerfile -t payguard/loan-service .`. Run it with `SPRING_DATASOURCE_URL="$LOAN_ORACLE_JDBC_URL"`, `SPRING_DATASOURCE_USERNAME="$LOAN_ORACLE_APP_USER"`, and `SPRING_DATASOURCE_PASSWORD="$LOAN_ORACLE_APP_PASSWORD"` from `.env`; its Flyway schema is applied only to the loan-owned Oracle instance.
13 changes: 13 additions & 0 deletions payguard-wallet-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# syntax=docker/dockerfile:1
FROM eclipse-temurin:25-jdk-alpine AS build
WORKDIR /workspace
COPY . .
RUN chmod +x mvnw && ./mvnw --batch-mode --no-transfer-progress -pl :payguard-wallet-service -am -DskipTests package

FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
RUN addgroup -S payguard && adduser -S payguard -G payguard
COPY --from=build /workspace/payguard-wallet-service/target/payguard-wallet-service-*.jar /app/app.jar
USER payguard
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "/app/app.jar"]
3 changes: 3 additions & 0 deletions payguard-wallet-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Wallet service

Build independently from the repository root with `docker build -f payguard-wallet-service/Dockerfile -t payguard/wallet-service .`. Run with `docker run --env-file .env -e SPRING_DATASOURCE_URL="$WALLET_ORACLE_JDBC_URL" -e SPRING_DATASOURCE_USERNAME="$WALLET_ORACLE_APP_USER" -e SPRING_DATASOURCE_PASSWORD="$WALLET_ORACLE_APP_PASSWORD" -p 8081:8080 payguard/wallet-service`.
Loading