Skip to content

syncfs/ledger-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ledger-engine

System Design

A payment transaction engine built on double-entry bookkeeping principles. Every transfer produces debit and credit journal entries against an append-only ledger, giving full financial traceability from day one.

Features

  • Double-entry bookkeeping — every transaction writes a debit and credit journal entry atomically
  • Idempotent transfers — duplicate requests with the same key return the cached result instead of double-processing
  • Deadlock-safe locking — account rows are locked in deterministic UUID order before any balance mutation
  • Append-only audit trail — journal entries are never updated or deleted
  • JWT authentication — all endpoints require a signed Bearer token
  • Structured logging — JSON logs with request ID, subject, method, path, status, and duration
  • Graceful shutdown — in-flight requests drain before the server exits

Stack

  • Gonet/http, no framework
  • PostgreSQL — via pgx/v5 connection pool
  • JWTgolang-jwt/jwt/v5

Getting Started

Prerequisites: Go 1.25+, Docker

# Start PostgreSQL
docker compose up -d

# Apply migrations
psql postgresql://ledger:ledger@localhost:5432/ledger -f migrations/001_create_accounts.sql
psql postgresql://ledger:ledger@localhost:5432/ledger -f migrations/002_create_transactions.sql
psql postgresql://ledger:ledger@localhost:5432/ledger -f migrations/003_create_journal_entries.sql
psql postgresql://ledger:ledger@localhost:5432/ledger -f migrations/004_create_idempotency_keys.sql

# Run
JWT_SECRET=your_secret go run ./cmd/app

Environment Variables

Variable Default Description
DB_HOST localhost PostgreSQL host
DB_PORT 5432 PostgreSQL port
DB_USER ledger PostgreSQL user
DB_PASSWORD ledger PostgreSQL password
DB_NAME ledger PostgreSQL database name
JWT_SECRET changeme HMAC secret for JWT
APP_PORT 8080 HTTP listen port

API

All requests require Authorization: Bearer <token>.

Accounts

POST /v1/accounts

Creates a new account with zero balance. Returns the account object.

GET /v1/accounts/{id}

Returns account by UUID.

Transactions

POST /v1/transactions
Content-Type: application/json

{
  "idempotency_key": "unique-key",
  "source_account_id": "<uuid>",
  "destination_account_id": "<uuid>",
  "amount": 1000
}

Transfers amount (integer, smallest currency unit) from source to destination. Idempotent — repeating the same idempotency_key returns the original result. Idempotency keys expire after 24 hours.

GET /v1/transactions/{id}

Returns transaction by UUID.

Error Codes

Code Status Description
MISSING_IDEMPOTENCY_KEY 400 idempotency_key field is empty
INVALID_AMOUNT 400 Amount is zero or negative
SAME_ACCOUNT 400 Source and destination are the same
ACCOUNT_NOT_FOUND 404 One or both accounts do not exist
INSUFFICIENT_BALANCE 422 Source account has insufficient funds
IDEMPOTENCY_CONFLICT 409 Key claimed but result not yet committed
OPTIMISTIC_LOCK_CONFLICT 409 Concurrent modification detected
UNAUTHORIZED 401 Missing or invalid JWT

Schema

accounts(id, balance, version, created_at)
transactions(id, source_account_id, destination_account_id, amount, status, created_at)
journal_entries(id, transaction_id, account_id, amount, direction, created_at)
idempotency_keys(key, response, expires_at)

balance has a CHECK (balance >= 0) constraint at the database level. Expired idempotency keys are cleaned up hourly by a background goroutine.

About

A production-grade double-entry ledger engine built in Go, designed for safe concurrent transfers with full financial auditability.

Resources

Stars

Watchers

Forks

Contributors

Languages