Skip to content

diegolopes98/banking-ledger

Repository files navigation

Banking Ledger API

A banking transaction ledger built with Clean Architecture, demonstrating a robust account transaction system with CQRS-style separation of read and write concerns.


WARNING!

Integration tests require a container runtime (Docker, Podman, etc.) for Testcontainers. Tests will be skipped if no container runtime is configured.


Table of Contents

Architecture Overview

This project follows Clean Architecture principles with clear separation between domain logic, application use cases, and infrastructure concerns. The application is organized as a multi-module Gradle project:

ledger/
│
├── domain/            # Core business logic & entities
│   ├── account/       # Account aggregate
│   ├── transaction/   # Transaction aggregate
│   └── pagination/    # Shared pagination utilities
│
├── application/       # Use cases & orchestration
│   └── account/
│       └── transaction/
│           ├── create/    # Create transaction use case
│           └── list/      # List transactions use case
│
└── infrastructure/    # External adapters
    ├── adapter/       # JPA persistence adapters
    ├── configuration/ # Spring configuration
    └── rest/          # REST API controllers

Architectural Patterns

  • Clean Architecture: Enforced dependency rules with domain at the center
  • CQRS-Style: Separate read/write data sources with routing capability
  • Use Case Pattern: Application logic encapsulated in discrete use cases
  • Gateway Pattern: Domain interfaces implemented by infrastructure adapters

Key Design Decisions

All architectural decisions are documented in Architecture Decision Records (ADRs):

Tech Stack

Core

  • Java 25 - Latest LTS version
  • Spring Boot 4.0.1 - Framework foundation
  • PostgreSQL - SQL ACID database
  • Flyway - Database migration management
  • Gradle - Build automation

Persistence

  • Spring Data JPA - Data access layer
  • Hibernate - ORM implementation
  • Custom DataSource Routing - Read/write database separation

Observability

  • OpenTelemetry - Distributed tracing
  • Micrometer - Metrics instrumentation
  • Logstash Encoder - Structured JSON logging
  • Spring Boot Actuator - Health checks and metrics
  • Grafana LGTM - Logs, graphs, traces, and metrics visualization

Testing

  • JUnit 5 - Test framework
  • Mockito - Mocking library
  • Testcontainers - Integration testing with PostgreSQL
  • JaCoCo - Java Code Coverage reporting

API & Documentation

  • SpringDoc OpenAPI - API documentation (Swagger UI)
  • Jakarta Bean Validation - Request validation
  • Bruno - API collection for manual testing

Getting Started

Prerequisites

  • Java 25+ (SDKMAN recommended)
  • Container runtime: Docker, Podman, or compatible alternative
  • Make (optional, for convenience commands)

Quick Start

1. Setup environment and run application

# Setup environment files
make setup-env

# Start dependencies (postgresql and otel-lgtm)
make compose-up-deps

# Run the application locally (for dev profile run migrations as well)
make run

This will:

  • Copy .env.sample to .env (if not exists)
  • Start PostgreSQL and OpenTelemetry collector via Container Compose
  • Start the application with Spring DevTools enabled

2. Alternative: Run everything in containers

# Setup all environment files
make setup-env setup-env-container

# Start full stack (postgresql + otel-lgtm + ledger app and migrations if dev profile set)
make compose-up-full

Available Make Commands

# Check all available commands
make help

Environment Variables

The application uses the following environment variables (defaults in .env.sample):

# Spring Profile
SPRING_PROFILES_ACTIVE=dev

# Server
HTTP_PORT=8080

# Database - Read-Only (queries)
DBRO_URL=jdbc:postgresql://localhost:5432/ledger
DBRO_APPLICATION_USER=root
DBRO_APPLICATION_PASSWORD=root

# Database - Read-Write (commands)
DBRW_URL=jdbc:postgresql://localhost:5432/ledger
DBRW_APPLICATION_USER=root
DBRW_APPLICATION_PASSWORD=root

# Database - Migrations
DBRW_MIGRATIONS_USER=root
DBRW_MIGRATIONS_PASSWORD=root

# OpenTelemetry
OTEL_EXPORTER_OTLP_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Container Runtime Configuration

The Makefile supports multiple container runtimes (docker, podman, nerdctl, etc.):

# Using Podman
export CONTAINER_RUNTIME=podman
make compose-up-deps

# Or inline
CONTAINER_RUNTIME=podman make compose-up-deps

API Documentation

Base URL

http://localhost:8080/api

Endpoints

1. Create Transaction

Initiates an outbound transaction from a given account to a beneficiary.

Endpoint: POST /v1/accounts/{id}/transactions

Path Parameters:

  • id (UUID, required) - Source account ID

Request Body:

{
  "beneficiaryName": "John Doe",
  "amount": 5000,
  "paymentReference": "Invoice #12345"
}

Field Descriptions:

  • beneficiaryName (string, required) - Recipient's full name
  • amount (integer, required) - Amount in minor units (cents)
  • paymentReference (string, optional) - Optional payment reference

Example Request:

curl --request POST \
  --url http://localhost:8080/api/v1/accounts/550e8400-e29b-41d4-a716-446655440000/transactions \
  --header 'Content-Type: application/json' \
  --data '{
    "beneficiaryName": "John Doe",
    "amount": 5000,
    "paymentReference": "Invoice #12345"
  }'

Response Codes:

  • 200 OK - Transaction created successfully
  • 422 Unprocessable Entity - Validation error (e.g., insufficient funds, invalid account)
  • 500 Internal Server Error - Internal Server Error

Business Rules:

  • Account must exist and have sufficient balance
  • Amount must be positive
  • Account balance is updated atomically with pessimistic locking

2. List Account Transactions

Lists all transactions for a given account, ordered by creation date (most recent first).

Endpoint: GET /v1/accounts/{id}/transactions

Path Parameters:

  • id (UUID, required) - Account ID

Query Parameters:

  • page (integer, optional, default: 0) - Page number (zero-indexed)
  • perPage (integer, optional, default: 10) - Items per page
  • dir (string, optional, default: DESC) - Sort direction (ASC or DESC)

Example Request:

curl --request GET \
  --url 'http://localhost:8080/api/v1/accounts/550e8400-e29b-41d4-a716-446655440000/transactions?page=0&perPage=10&dir=DESC'

Example Response:

{
  "currentPage": 0,
  "perPage": 10,
  "total": 25,
  "items": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "type": "DEBIT",
      "amount": 5000,
      "paymentReference": "Invoice #12345"
    },
    {
      "id": "8d1f7780-8536-51ef-a55c-f18gd2g01bf8",
      "type": "CREDIT",
      "amount": 2500,
      "paymentReference": "Payment for services"
    }
  ]
}

Response Codes:

  • 200 OK - Transactions listed successfully
  • 404 Not Found - Not Found Account ID
  • 422 Unprocessable Entity - Validation error (e.g., invalid account ID)
  • 500 Internal Server Error - Server error

Interactive API Documentation

OpenAPI/Swagger UI available at:

http://localhost:8080/api/swagger-ui.html

OpenAPI specification:

http://localhost:8080/api/api-docs

Bruno API Collection

Pre-configured API requests are available in the .bruno/ directory. Use Bruno to import and test the API endpoints.

Testing

Run All Tests

# Using Make
make test

# Using Gradle directly
./gradlew clean test

Test Coverage Report

After running tests, view the JaCoCo coverage report:

# Generate reports automatically when running tests
./gradlew test

# Open reports in browser
open infrastructure/build/reports/jacoco/test/html/index.html  # macOS
xdg-open infrastructure/build/reports/jacoco/test/html/index.html  # Linux

Test Structure

The project includes comprehensive test coverage:

  • Unit Tests: Domain entities and use cases with Mockito

    • domain/src/test/ - Domain entity validation and business rules
    • application/src/test/ - Use case logic with mocked gateways
  • Integration Tests: Repository layer with Testcontainers

    • infrastructure/src/test/ - JPA repositories with real PostgreSQL
  • End-to-End Tests: REST API with Spring MockMvc

    • infrastructure/src/test/ - Full request/response cycle testing

Test Requirements

  • Container runtime (Docker/Podman) must be running for integration tests
    • Tests use Testcontainers to spin up PostgreSQL automatically
    • If no container runtime is available, integration tests will be skipped

Observability

Health Check

curl http://localhost:8080/api/actuator/health

Example Response:

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP"
    },
    "diskSpace": {
      "status": "UP"
    }
  }
}

Prometheus Metrics

Application metrics exposed in:

curl http://localhost:8080/api/actuator/metrics

Available Metrics:

  • http_server_requests_* - HTTP request metrics
  • jvm_memory_* - JVM memory usage
  • jvm_threads_* - Thread pool metrics
  • hikaricp_connections_* - Database connection pool metrics
  • process_cpu_usage - CPU utilization

OpenTelemetry & Distributed Tracing

The application exports traces to Grafana LGTM stack:

Access Grafana:

http://localhost:3000
Username: admin
Password: admin

Features:

  • Distributed trace visualization
  • Request latency analysis
  • Database query performance
  • Service dependency mapping

Structured Logging

Application uses structured JSON logging with automatic trace ID propagation:

{
  "@timestamp": "2025-01-06T10:30:00.000Z",
  "level": "INFO",
  "logger_name": "com.banking.ledger.infrastructure.rest.controller.AccountsController",
  "message": "Successfully created transaction",
  "account_id": "550e8400-e29b-41d4-a716-446655440000",
  "transaction_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "amount": 5000,
  "trace_id": "a1b2c3d4e5f6g7h8",
  "span_id": "1234567890abcdef",
  "service.name": "ledger"
}

Available Actuator Endpoints

  • /actuator/health - Application health status
  • /actuator/info - Build information
  • /actuator/metrics - Available metrics list

Database

Database Migrations

Flyway migrations are located in infrastructure/src/main/resources/db/migration/:

  • V001__create_schema_ledger.sql - Create ledger schema
  • V002__create_enum_currency.sql - Currency enum type
  • V003__create_table_accounts.sql - Accounts table
  • V004__create_table_transactions.sql - Transactions table

Migrations run automatically on application startup or can be run manually:

./gradlew :infrastructure:flywayMigrate

Read/Write Database Separation

The application uses custom DataSource routing to separate read and write operations:

  • Write DataSource: All @Transactional methods with create/update operations
  • Read DataSource: Read-only queries for listing transactions

This architecture enables:

  • Future horizontal scaling with read replicas
  • Performance optimization by load balancing reads
  • Clear separation of concerns between commands and queries

Production Considerations

Implemented Production Features

  • Structured Logging: JSON logs with trace context for centralized log aggregation
  • Health Checks: Spring Actuator endpoints for container orchestration
  • Metrics Export: Prometheus-compatible metrics for monitoring
  • Distributed Tracing: OpenTelemetry integration for request tracing
  • Database Migrations: Flyway for safe, versioned schema changes
  • Validation: Jakarta Bean Validation on API inputs and domain validations
  • Error Handling: Structured error responses with appropriate HTTP status codes
  • API Documentation: OpenAPI/Swagger for API discovery

Follow-ups for Production Hardening

These improvements would be valuable for a production-ready system:

Security

  • Authentication & Authorization - OAuth2/JWT integration
  • API Rate Limiting - Bucket4j or similar for abuse prevention
  • Security Headers - HSTS, CSP, X-Frame-Options

Scalability

  • Caching Layer - Redis for frequently accessed data
  • Message Queue - Async transaction processing with Kafka/RabbitMQ
  • API Gateway - Centralized routing, authentication, and rate limiting

Resilience

  • Circuit Breakers - Resilience4j for failure isolation
  • Retry Policies - Exponential backoff for transient failures
  • Timeout Configuration - Request-level timeout strategies
  • Bulkhead Pattern - Resource isolation for critical operations
  • Graceful Degradation - Fallback mechanisms for service outages

Observability

  • Custom Business Metrics - Transaction volume, success rates, latency percentiles
  • Alerting - Alerts for critical conditions
  • APM Integration - New Relic, Datadog, or Dynatrace
  • Distributed Trace Sampling - Intelligent trace sampling for high-volume systems

Testing & Quality

  • Contract Testing - For API compatibility guarantees
  • Performance Testing - Gatling or JMeter for load testing
  • Chaos Engineering - Failure injection for resilience validation

DevOps

  • CI/CD Pipeline - Automated testing, building, and deployment
  • Container Orchestration - Kubernetes for production deployment
  • Blue-Green Deployment - Zero-downtime deployments
  • Infrastructure as Code - Terraform or similar for reproducible environments

Compliance & Governance

  • Audit Logging - Immutable audit trail for regulatory compliance
  • Data Retention Policies - Automated data archival and purging
  • Multi-region Support - Data residency requirements

TODOS

  • Multiple currency accounts with wallets for each one
  • Account identifier for transactions instead of using name and origin currency

About

banking ledger API for portfolio

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors