Skip to content

HomelessCoder/financial-queue-system

Repository files navigation

Financial Queue System

A financial transaction system with an asynchronous command queue for processing operations.

πŸš€ Quick Start

# Clone and start services (PostgreSQL + 3 workers)
git clone https://github.com/HomelessCoder/financial-queue-system.git
cd financial-queue-system
docker compose up -d

Step-by-Step Testing Flow

Terminal 1 - Watch workers process commands in real-time:

docker compose logs -f

Terminal 2 - Execute the following commands:

Step 1: Deposit funds to John Doe's account

# Deposit $500 to John Doe
docker compose exec worker-1 php bin/console trade:deposit-funds 0199dfbd-9da7-7e4c-8754-13fa5a9af89c 50000 USD

# Check account balance
docker compose exec postgres psql -U fqs -d fqs -c "SELECT * FROM accounts WHERE id = '0199dfbd-9da7-7e4c-8754-13fa5a9af89c';"

Step 2: Charge funds from the account

# Charge $200 from John Doe
docker compose exec worker-1 php bin/console trade:charge-funds 0199dfbd-9da7-7e4c-8754-13fa5a9af89c 20000 USD "Service fee"

# Check account and transactions
docker compose exec postgres psql -U fqs -d fqs -c "SELECT * FROM accounts WHERE id = '0199dfbd-9da7-7e4c-8754-13fa5a9af89c';"
docker compose exec postgres psql -U fqs -d fqs -c "SELECT * FROM transactions WHERE target_account_id = '0199dfbd-9da7-7e4c-8754-13fa5a9af89c' ORDER BY timestamp DESC LIMIT 5;"

Step 3: Create a hold

# Create a $300 hold on John Doe's account (expires in 30 minutes)
docker compose exec worker-1 php bin/console trade:place-hold 0199dfbd-9da7-7e4c-8754-13fa5a9af89c 30000 USD "Pending payment" --expires-in-minutes=30

# Check account, transactions, and holds
docker compose exec postgres psql -U fqs -d fqs -c "SELECT * FROM accounts WHERE id = '0199dfbd-9da7-7e4c-8754-13fa5a9af89c';"
docker compose exec postgres psql -U fqs -d fqs -c "SELECT * FROM transactions WHERE target_account_id = '0199dfbd-9da7-7e4c-8754-13fa5a9af89c' ORDER BY timestamp DESC LIMIT 5;"
docker compose exec postgres psql -U fqs -d fqs -c "SELECT * FROM holds WHERE account_id = '0199dfbd-9da7-7e4c-8754-13fa5a9af89c';"

# Capture the hold (use the hold ID from the previous output)
# Example hold ID: 0199e556-cdf6-7262-b67c-48ce3f3a2ff4
docker compose exec worker-1 php bin/console trade:capture-hold <hold-id>

Step 4: Transfer funds to Jane Smith

# Transfer $100 from John Doe to Jane Smith
docker compose exec worker-1 php bin/console trade:transfer-funds 0199dfbd-9da7-7e4c-8754-13fa5a9af89c 0199dfbd-9da7-78b9-b132-8843318e7348 10000 USD

# Check both accounts
docker compose exec postgres psql -U fqs -d fqs -c "SELECT id, balance_units, balance_currency_iso, held_balance_units FROM accounts WHERE id IN ('0199dfbd-9da7-7e4c-8754-13fa5a9af89c', '0199dfbd-9da7-78b9-b132-8843318e7348');"

πŸ’‘ Tip: Watch Terminal 1 to see workers pick up and process commands in real-time!

πŸ—οΈ Key Features

βœ… Parallel Worker Execution

Workers safely run in multiple processes simultaneously:

# Docker Compose runs 3 workers in parallel
docker compose up -d

# Workers use PostgreSQL FOR UPDATE SKIP LOCKED to prevent race conditions
# Each worker processes different commands concurrently
# Implementation: src/CommandQueue/Infra/CommandQueueRepository.php

βœ… Scalable Architecture

Workers can run for any duration and in any quantity:

# Add more workers in docker-compose.yml
# Workers automatically pick up commands using SKIP LOCKED
# No coordination required between worker instances

βœ… Safe Transaction Processing

All operations are protected against double processing and double writes:

  • Database Row Locking: FOR UPDATE SKIP LOCKED prevents concurrent command processing
  • Idempotency Keys: Optional UUID keys prevent duplicate operations (charge, deposit, transfer, hold)
  • Transaction Integrity: All operations wrapped in database transactions with automatic rollback on failure
  • Status Tracking: Command lifecycle (pending β†’ processing β†’ completed/failed)

Example with idempotency key:

# If retried, same idempotency key prevents duplicate charge
docker compose exec worker-1 php bin/console trade:charge-funds <account-id> 5000 USD "Purchase" --idempotency-key=<uuid>

βœ… Unit Tests

# Run test suite
vendor/bin/phpunit test/

# Code quality checks
make phpstan
make codestyle

πŸ› οΈ Technology Stack

Component Technology
Language PHP 8.4+
Database PostgreSQL 17
Framework Power Modules Framework 2.1
CLI Symfony Console 7.3
Container Docker & Docker Compose
Testing PHPUnit 12.4
Code Quality PHPStan 2.1, PHP CS Fixer 3.88

πŸ”„ Production-Ready Alternatives

This project was intentionally built with custom implementations to demonstrate a deep understanding of async architecture patterns in PHP. Below is a comparison of what was implemented versus production-grade Symfony/ecosystem alternatives:

Custom Implementation Production Alternative Notes
Custom Command Queue (CommandQueue, Worker, CommandQueueRepository) Symfony Messenger (symfony/messenger) Messenger provides transport abstraction (AMQP, Redis, Doctrine), retry strategies, failure handling, and middleware pipeline. Custom implementation demonstrates understanding of queue mechanics, row locking, and exponential backoff.
Custom DI Container (Power Modules Framework) Symfony DependencyInjection (symfony/dependency-injection) Symfony's DI offers autowiring, compiler passes, service tagging, and extensive configuration options. Power Modules was used to show modular architecture patterns.
PostgreSQL LISTEN/NOTIFY (pg_notify in repository) Redis Pub/Sub or Symfony Messenger with Redis Transport PostgreSQL NOTIFY works but Redis is more suitable for high-throughput message passing. Demonstrates understanding of database-level notification mechanisms.
Custom AbstractRepository (PDO-based with manual hydration) Doctrine ORM (doctrine/orm) Doctrine provides entity mapping, relationships, migrations, lazy loading, and query builder. Custom repository shows understanding of data mapping patterns and transaction management.
Manual Serialization (serialize()/unserialize()) Symfony Serializer (symfony/serializer) Symfony Serializer supports JSON/XML/YAML, normalizers, and is more secure than native PHP serialization. Custom approach demonstrates message passing fundamentals.
Custom Id Value Object (UUID v7 wrapper) Symfony Uid Component (symfony/uid) Symfony Uid provides Uuid, Ulid with built-in validation and factories. Custom implementation shows value object pattern and UUID v7 understanding.
Custom Amount Value Object Money PHP (moneyphp/money) Money library handles currency conversion, formatting, allocation, and precise arithmetic. Custom implementation demonstrates understanding of financial data modeling.
Manual Console Command Registration Symfony Console AutoDiscovery with #[AsCommand] + CommandLoader Symfony can auto-register commands via service tags. Manual registration demonstrates understanding of console application lifecycle.
Custom CompositeCommandHandler (manual handler registration) Symfony Messenger Handler Locator with #[AsMessageHandler] Messenger uses service locator pattern with attribute-based discovery. Custom composite shows understanding of command/handler patterns.
FOR UPDATE SKIP LOCKED (manual SQL) Symfony Lock Component (symfony/lock) or Messenger's native locking Symfony Lock provides store adapters (Redis, Database, Semaphore). Direct SQL demonstrates low-level concurrency control understanding.

πŸ“ Key Takeaways

Why Custom Implementation?

  • Demonstrates intimate knowledge of async patterns, database locking, and queue mechanics
  • Shows ability to build production-grade features from scratch
  • Proves understanding of the underlying concepts that frameworks abstract away

When to Use Symfony Components in Production?

  • Symfony Messenger: Industry standard for async processing, battle-tested, extensive documentation
  • Doctrine ORM: Rich feature set, community support, reduces boilerplate dramatically
  • Symfony DI: Powerful autowiring and configuration management
  • Symfony Serializer: Security, flexibility, and ecosystem integration

Trade-offs:

  • Custom code = Full control + Educational value - Maintenance overhead - Missing ecosystem features
  • Symfony components = Faster development + Community support + Rich features - Learning curve - Framework coupling

This project strikes a balance: it uses Symfony Console (industry standard) while implementing core async/queue logic from scratch to demonstrate architectural competence. In a production system, I would recommend Symfony Messenger with a Redis or RabbitMQ transport for the queue system, and Doctrine ORM for data persistence.

🚒 Deployment

Development (Docker Compose)

docker compose up -d

Production Options

Option 1: Supervisor

[program:fqs-worker]
command=/app/bin/console command-queue:run-worker
process_name=%(program_name)s_%(process_num)02d
numprocs=3
autostart=true
autorestart=true

Option 2: Systemd

[Service]
ExecStart=/app/bin/console command-queue:run-worker
Restart=always

Option 3: Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fqs-worker
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: worker
        command: ["php", "bin/console", "command-queue:run-worker"]

πŸ’» Available Commands

Note: Commands can be run either directly (if PHP is installed) or via Docker. Docker method works without PHP installed on the host system.

Trade Operations

# Charge funds from an account (reason is optional)
docker compose exec worker-1 php bin/console trade:charge-funds <account-id> <units> <currency> [<reason>]

# Deposit funds to an account
docker compose exec worker-1 php bin/console trade:deposit-funds <account-id> <units> <currency>

# Transfer funds between accounts
docker compose exec worker-1 php bin/console trade:transfer-funds <source-account-id> <destination-account-id> <units> <currency>

# Place and capture holds
docker compose exec worker-1 php bin/console trade:place-hold <account-id> <units> <currency> <reason> [--expires-in-minutes=<minutes>]
docker compose exec worker-1 php bin/console trade:capture-hold <hold-id>

# All commands support optional --idempotency-key flag
docker compose exec worker-1 php bin/console trade:charge-funds <account-id> <units> <currency> <reason> --idempotency-key=<uuid>

Note: units is the amount in smallest currency units (e.g., cents for USD). So 5000 units = $50.00 USD.

Alternative: Direct execution (requires PHP 8.4+ installed)
bin/console trade:charge-funds <account-id> <units> <currency> [<reason>]
bin/console trade:deposit-funds <account-id> <units> <currency>
bin/console trade:transfer-funds <source-account-id> <destination-account-id> <units> <currency>
bin/console trade:place-hold <account-id> <units> <currency> <reason> [--expires-in-minutes=<minutes>]
bin/console trade:capture-hold <hold-id>

Worker Management

# Start a worker (used by Docker Compose, runs indefinitely)
docker compose exec worker-1 php bin/console command-queue:run-worker

# Or directly if PHP is installed:
# bin/console command-queue:run-worker

πŸ§ͺ Test Accounts

The system includes 2 pre-seeded test accounts (each with $1,000.00 USD):

Name Account ID
John Doe 0199dfbd-9da7-7e4c-8754-13fa5a9af89c
Jane Smith 0199dfbd-9da7-78b9-b132-8843318e7348

πŸ“ Architecture

Console Commands β†’ Command Queue (PostgreSQL)
                         ↓
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              ↓          ↓          ↓
          Worker 1   Worker 2   Worker 3
              ↓          ↓          ↓
         Command Handlers (Business Logic)
              ↓          ↓          ↓
         Domain Services & Repositories

Key Components

  • CommandQueue: PostgreSQL table with status tracking
  • Worker: Polls queue, executes commands via handlers
  • CompositeCommandHandler: Invokes all registered handlers; each handler checks if it supports the command type
  • Repositories: Data access layer with transaction support
  • Domain Models: Account, Transaction, User, Hold

πŸ”§ Development

# Run tests
make test

# Static analysis
make phpstan

# Code style check
make codestyle

# View logs
docker compose logs -f

# Database access
docker compose exec postgres psql -U fqs -d fqs

πŸ“Š Docker Services

Service Container Name Purpose
postgres fqs-postgres PostgreSQL 17 database
worker-1 fqs-worker-1 Command queue worker #1
worker-2 fqs-worker-2 Command queue worker #2
worker-3 fqs-worker-3 Command queue worker #3

About

DEMO. A financial transaction system with an asynchronous command queue for processing operations.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages