Skip to content

algorithm108/tiny-ledger

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ledger API

A tiny in-memory REST API for recording money movements.

A small ledger that supports deposits, withdrawals, balance queries and transaction history.

This document describes scopes, assumptions and trade-offs.


Assumptions and Design Decisions

Single ledger / single account

This application manages one ledger, which represents one account balance.

I intentionally did not introduce User, Account or Wallet entities because the assignment only asks for deposits, withdrawals, current balance and transaction history. Adding multiple accounts would require additional API design, account identity, ownership rules and transfer semantics.

Deposits and withdrawals as external money movements

Deposits and withdrawals are treated as external movements into or out of the ledger.

  • A deposit increases the ledger balance.
  • A withdrawal decreases the ledger balance.

Transfers between accounts are intentionally out of scope because they would require modelling at least two accounts and ensuring both debit and credit entries are created atomically.

Positive transaction amounts

Transaction amounts are always positive.

The transaction type defines the effect on the balance:

  • DEPOSIT adds the amount.
  • WITHDRAWAL subtracts the amount.

One currency

All amounts are assumed to be in one currency.

Multi-currency support is intentionally excluded because it introduces extra rules such as currency codes, decimal precision per currency, exchange rates and conversion logic.

BigDecimal for money

Monetary values are represented with BigDecimal.

double and float are intentionally avoided because floating-point arithmetic can introduce rounding errors.

Amounts are validated to be:

  • present
  • greater than zero
  • no more than two decimal places

Append-only transaction history

Transactions are append-only.

The API does not support updating or deleting transactions because transaction history acts as the audit log for the ledger.

In-memory storage

The application uses in-memory data structures, as suggested by the assignment.

Data is lost when the application restarts.

I considered H2, but decided against it because it would shift the exercise from a tiny ledger API into persistence design. Since the assignment explicitly suggested maps or arrays, I kept storage in memory and documented how I would handle persistence and database concurrency in production.

Concurrency handling

The service stores both current balance and transaction history. Both are updated together inside the same critical section (synchronized block) to keep them consistent.

The lock protects:

  1. Check the current balance
  2. Calculate the new balance
  3. Create the transaction record
  4. Update the balance
  5. Append the transaction to history

This prevents race conditions such as two concurrent withdrawals both seeing the same old balance.

This is a simple single-JVM solution. In production, consistency would be handled with database transactions or optimistic locking.

Error handling

  • Validation errors → 400 Bad Request
  • Withdrawal exceeds balance → 409 Conflict

Intentionally Excluded Features

Feature Reason
Users Authentication and authorization are out of scope
Accounts Assignment can be satisfied with a single ledger
Currency Multi-currency introduces rules not required here
Database Assignment suggests in-memory storage
Docker App should run locally without extra software
Event Sourcing/CQRS Tiny implementation, not a production platform
Monitoring/Logging Not expected for this assignment

Quick Start

Prerequisites

  • Java 26

Run the application

./gradlew bootRun

App starts at http://localhost:8080

Run tests

./gradlew test

Swagger UI

Once running, visit:

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

API Endpoints

Method Endpoint Description
POST /transactions Create a deposit or withdrawal
GET /transactions Get transaction history
GET /balance Get current balance

Create a deposit

curl -X POST http://localhost:8080/transactions \
  -H "Content-Type: application/json" \
  -d '{"type": "DEPOSIT", "amount": 100.00}'
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "DEPOSIT",
  "amount": 100.00,
  "balanceAfter": 100.00,
  "createdAt": "2026-05-20T10:30:00Z"
}

Create a withdrawal

curl -X POST http://localhost:8080/transactions \
  -H "Content-Type: application/json" \
  -d '{"type": "WITHDRAWAL", "amount": 30.00}'
{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "type": "WITHDRAWAL",
  "amount": 30.00,
  "balanceAfter": 70.00,
  "createdAt": "2026-05-20T10:31:00Z"
}

Get balance

curl http://localhost:8080/balance
{
  "balance": 70.00
}

Get transaction history

curl http://localhost:8080/transactions
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "DEPOSIT",
    "amount": 100.00,
    "balanceAfter": 100.00,
    "createdAt": "2026-05-20T10:30:00Z"
  },
  {
    "id": "550e8400-e29b-41d4-a716-446655440001",
    "type": "WITHDRAWAL",
    "amount": 30.00,
    "balanceAfter": 70.00,
    "createdAt": "2026-05-20T10:31:00Z"
  }
]

Error: Insufficient balance

curl -X POST http://localhost:8080/transactions \
  -H "Content-Type: application/json" \
  -d '{"type": "WITHDRAWAL", "amount": 1000.00}'
{
  "timestamp": "2026-05-20T10:32:00Z",
  "status": 409,
  "error": "INSUFFICIENT_BALANCE",
  "message": "Insufficient balance: current 70.00, requested withdrawal 1000.00"
}

Error: Invalid request

curl -X POST http://localhost:8080/transactions \
  -H "Content-Type: application/json" \
  -d '{"type": "DEPOSIT", "amount": -50.00}'
{
  "timestamp": "2026-05-20T10:33:00Z",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "details": ["amount: Amount must be greater than zero"]
}

Production Improvements

If this were a production system:

  • Persistent storage — PostgreSQL or similar
  • Database transactions — atomicity of balance updates
  • Row-level / optimistic locking — prevent race conditions
  • Idempotency keys — prevent duplicate transactions from retries
  • Multiple accounts — accounts with unique identifiers
  • Multi-currency — currency codes, exchange rates
  • Pagination — for transaction history
  • Authentication - some modern framework like Oauth 2.0
  • Audit/reversal flows — transaction reversals with audit trails
  • Observability — metrics, tracing, health checks
  • Structured logging — JSON logging for production debugging

Tech Stack

  • Java 26
  • Spring Boot 4.0.6
  • Gradle
  • Spring Web MVC
  • Bean Validation
  • Lombok
  • JUnit 5
  • SpringDoc OpenAPI (Swagger)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages