Architectural Patterns and Design Principles
Referenced Files in This Document - server.py - app/blockchain/manager.py - app/blockchain/base.py - app/blockchain/ethereum.py - app/blockchain/w3.py - app/api/v1/payments.py - app/api/dependencies.py - app/services/blockchain/scanner.py - app/services/webhook.py - app/workers/listener.py - app/workers/sweeper.py - app/workers/webhook.py - app/utils/crypto.py - app/db/models/payment.py - app/core/config.py
Table of Contents
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendices
Introduction
This document analyzes the cTrip Payment Gateway codebase to extract and explain advanced architectural patterns and design principles that guide system design for distributed payment processing. It focuses on: - Factory pattern implementation in blockchain manager - Observer-like notification via webhook worker - Worker pattern for background task processing - Dependency injection via FastAPI lifespan and dependency providers - Service layer architecture and layered system design - Asynchronous operation handling, error propagation, and graceful degradation - Architectural decision records, trade-offs, and design rationale - Anti-patterns, refactoring strategies, and maintainability considerations
Project Structure
The system follows a layered architecture: - API layer (FastAPI routers) handles HTTP requests and delegates to services - Service layer encapsulates business logic (scanning, sweeping, webhook delivery) - Workers layer runs background tasks using ARQ tasks - Blockchain abstraction layer manages multiple chains via a factory-style initializer - Persistence layer uses SQLAlchemy ORM models - Utilities and configuration provide cryptographic and environment utilities
payments.py"] end subgraph "Service Layer" S1["ScannerService
scanner.py"] S2["WebhookService
services/webhook.py"] end subgraph "Workers Layer" W1["listen_for_payments
workers/listener.py"] W2["sweep_funds
workers/sweeper.py"] W3["send_webhook_task
workers/webhook.py"] end subgraph "Blockchain Abstraction" B1["BlockchainBase
blockchain/base.py"] B2["EthereumBlockchain
blockchain/ethereum.py"] BM["get_blockchains()
blockchain/manager.py"] BW["get_w3()
blockchain/w3.py"] end subgraph "Persistence" M1["Payment Model
db/models/payment.py"] end subgraph "Config & Utils" C1["Settings
core/config.py"] U1["HDWalletManager
utils/crypto.py"] end R1 --> S1 R1 --> U1 S1 --> M1 S1 --> BW S2 --> W3 W1 --> S1 W2 --> S1 W3 --> S2 BW --> BM BM --> B1 B2 --> B1 C1 --> BM C1 --> W1 C1 --> W2 C1 --> W3
Diagram sources - server.py - app/api/v1/payments.py - app/api/dependencies.py - app/services/blockchain/scanner.py - app/services/webhook.py - app/workers/listener.py - app/workers/sweeper.py - app/workers/webhook.py - app/blockchain/manager.py - app/blockchain/base.py - app/blockchain/ethereum.py - app/blockchain/w3.py - app/db/models/payment.py - app/core/config.py - app/utils/crypto.py
Section sources - server.py - app/core/config.py
Core Components
- Blockchain abstraction and factory:
- BlockchainBase defines async operations for balance, gas estimation, transaction building, signing, and receipts.
- EthereumBlockchain extends base with chain-specific defaults.
- get_blockchains() constructs a registry of blockchain instances keyed by chain name.
- get_w3() resolves AsyncWeb3 clients from the registry.
- Service layer:
- ScannerService scans blocks for incoming payments and confirms them after required confirmations, emitting webhooks.
- WebhookService sends signed webhook payloads asynchronously.
- Workers:
- listen_for_payments and sweep_funds are ARQ tasks that run periodic scanning and sweeping cycles.
- send_webhook_task is a ARQ task that delivers webhooks asynchronously.
- API and DI:
- FastAPI lifespan initializes global state (blockchains, HD wallet) and seeds chain states.
- get_blockchains() and get_hdwallet() are dependency providers that inject shared resources.
- Persistence:
- Payment model tracks payment lifecycle and metadata.
- Utilities:
- HDWalletManager supports deterministic address derivation for payment addresses.
Section sources - app/blockchain/base.py - app/blockchain/ethereum.py - app/blockchain/manager.py - app/blockchain/w3.py - app/services/blockchain/scanner.py - app/services/webhook.py - app/workers/listener.py - app/workers/sweeper.py - app/workers/webhook.py - app/api/dependencies.py - app/db/models/payment.py - app/utils/crypto.py - server.py
Architecture Overview
The system employs a layered, event-driven design: - API layer orchestrates request handling and delegates to services. - Service layer encapsulates domain logic and coordinates persistence and blockchain interactions. - Workers run periodic tasks independently using ARQ tasks, decoupling long-running work from request latency. - Blockchain abstraction centralizes chain-specific concerns behind a unified interface. - Configuration drives chain selection and runtime behavior.
payments.py" participant DI as "Dependencies
dependencies.py" participant DB as "SQLAlchemy Session" participant HDW as "HDWalletManager
crypto.py" participant BG as "ARQ Tasks
listener/sweeper" Client->>API : "POST /api/v1/payments/" API->>DI : "get_blockchains(), get_hdwallet()" DI-->>API : "blockchains, hdwallet" API->>HDW : "derive payment address" API->>DB : "create Payment record" DB-->>API : "saved Payment" API-->>Client : "201 Created Payment" Note over BG,DB : "Background scanning and sweeping" BG->>DB : "scan blocks, update statuses" BG->>DB : "confirm payments, emit webhooks"
Diagram sources - app/api/v1/payments.py - app/api/dependencies.py - app/utils/crypto.py - app/workers/listener.py - app/workers/sweeper.py
Detailed Component Analysis
Factory Pattern in Blockchain Manager
The blockchain manager implements a factory that constructs chain-specific blockchain clients from configuration. It selects implementations based on chain name and falls back to a default when configuration is missing.
Key characteristics: - Centralized instantiation logic in get_blockchains() - Registry pattern: returns a dictionary of chain name to blockchain instance - Extensibility: adding new chains requires updating the factory switch and ensuring a subclass of BlockchainBase exists
Diagram sources - app/blockchain/base.py - app/blockchain/ethereum.py - app/blockchain/manager.py
Section sources - app/blockchain/manager.py - app/blockchain/base.py - app/blockchain/ethereum.py
Observer Pattern in Webhook Notifications
The system uses an observer-like pattern for notifications: - ScannerService observes payment state changes and triggers observers (webhook delivery). - send_webhook_task acts as an observer actor that asynchronously delivers events.
Diagram sources - app/services/blockchain/scanner.py - app/workers/webhook.py - app/services/webhook.py
Section sources - app/services/blockchain/scanner.py - app/workers/webhook.py - app/services/webhook.py
Worker Pattern in Background Task Processing
Background tasks are implemented as ARQ tasks: - listen_for_payments periodically scans for incoming payments and updates statuses - sweep_funds periodically sweeps confirmed payments - send_webhook_task asynchronously delivers webhook notifications
Diagram sources - app/workers/listener.py - app/workers/sweeper.py - app/workers/webhook.py
Section sources - app/workers/listener.py - app/workers/sweeper.py - app/workers/webhook.py
Dependency Injection Patterns
The system uses FastAPI’s lifespan and dependency injection: - Global initialization in lifespan sets app.state.blockchains and app.state.hdwallet - get_blockchains() and get_hdwallet() are dependency providers that raise explicit errors if not initialized - This ensures predictable resource sharing and avoids magic globals inside request handlers
Diagram sources - server.py - app/api/dependencies.py
Section sources - server.py - app/api/dependencies.py
Service Layer Architecture and Layered Design
- API layer validates inputs and delegates to services
- Services encapsulate business logic and coordinate persistence and blockchain interactions
- Workers isolate long-running tasks from request paths
- Clear separation of concerns improves testability and maintainability
Diagram sources - app/api/v1/payments.py - app/services/blockchain/scanner.py - app/workers/listener.py
Section sources - app/api/v1/payments.py - app/services/blockchain/scanner.py
Asynchronous Operations, Error Propagation, and Graceful Degradation
- Asynchronous blockchain operations are implemented with AsyncWeb3 and awaited in services and workers
- Error propagation:
- ScannerService logs detection and confirmation outcomes; exceptions are handled per-chain iteration
- WebhookService returns booleans and logs failures; send_webhook_task raises to trigger ARQ retries
- BlockchainBase wraps connection and gas estimation in try/catch blocks
- Graceful degradation:
- Gas estimation falls back to defaults when provider calls fail
- Missing chain configuration falls back to a default chain
- Webhook delivery is retried by ARQ; absence of webhook URL does not block confirmation
Diagram sources - app/blockchain/base.py - app/blockchain/manager.py - app/services/webhook.py - app/workers/webhook.py
Section sources - app/blockchain/base.py - app/blockchain/manager.py - app/services/webhook.py - app/workers/webhook.py
Architectural Decision Records and Trade-Offs
- Factory vs. DI container:
- Chosen: explicit factory for deterministic chain registration from configuration
- Trade-off: less dynamic than a full DI framework; easier to audit and test
- Observer-like webhooks:
- Chosen: separate actor for delivery to decouple from confirmation logic
- Trade-off: adds operational complexity; mitigated by retries and logging
- Worker pattern:
- Chosen: ARQ tasks for periodic tasks to avoid blocking API threads
- Trade-off: requires Redis; increases deployment complexity
- Gas estimation fallback:
- Chosen: conservative defaults to keep flows moving
- Trade-off: potential overpayment; acceptable for UX continuity
[No sources needed since this section synthesizes decisions without quoting specific code]
Anti-Patterns, Refactoring Strategies, and Maintainability
- Anti-patterns observed:
- Magic globals: resolved by injecting via FastAPI lifespan and dependencies
- Inline configuration parsing: centralized in Settings with validators
- Blocking I/O in actors: addressed by using async operations and avoiding blocking loops
- Refactoring strategies:
- Introduce a dedicated BlockchainRegistry singleton to centralize chain resolution and caching
- Extract webhook signing into a shared utility to reduce duplication
- Add circuit breaker around external RPC calls to prevent cascading failures
- Maintainability:
- Keep chain-specific subclasses minimal and delegate heavy logic to base class
- Use typed configuration and environment validation to catch misconfiguration early
- Add structured logging and metrics for worker throughput and error rates
[No sources needed since this section provides general guidance]
Dependency Analysis
The system exhibits low coupling and high cohesion: - API depends on services and dependencies; services depend on models and blockchain abstractions - Workers depend on services and configuration; they do not directly touch API routes - BlockchainBase is the core abstraction; concrete chains extend it
Diagram sources - app/api/v1/payments.py - app/services/blockchain/scanner.py - app/workers/listener.py - app/blockchain/base.py - app/core/config.py
Section sources - app/api/v1/payments.py - app/services/blockchain/scanner.py - app/workers/listener.py - app/blockchain/base.py - app/core/config.py
Performance Considerations
- Batch scanning: ScannerService processes blocks in batches to limit per-cycle work
- Gas cache: BlockchainBase caches gas price for a short duration to reduce RPC calls
- Async I/O: All blockchain operations are asynchronous to maximize throughput
- Retry and timeouts: WebhookService and AsyncWeb3 providers set timeouts to bound latency
- Actor scheduling: Workers schedule next runs with fixed delays to regulate load
[No sources needed since this section provides general guidance]
Troubleshooting Guide
Common issues and diagnostics: - Blockchain connectivity: - Use is_connected() checks and log provider URL errors - Gas estimation failures: - Confirm fallback defaults are applied; inspect warnings and adjust buffers - Webhook delivery failures: - Verify signatures and payload correctness; check actor retries and logs - Worker scheduling: - Ensure Redis availability and actor startup; monitor scheduled runs
Section sources - app/blockchain/base.py - app/blockchain/base.py - app/services/webhook.py - app/workers/webhook.py
Conclusion
The cTrip Payment Gateway demonstrates robust architectural patterns suited for distributed payment processing: - Factory pattern for extensible blockchain client management - Observer-like webhook delivery via asynchronous actors - Worker pattern for reliable background processing - Clean dependency injection via FastAPI lifespan and providers - Strong separation of concerns across layers - Practical error handling and graceful degradation strategies
These patterns provide a blueprint for building scalable, maintainable payment infrastructure with clear boundaries, observable behavior, and resilient operations.
[No sources needed since this section summarizes without analyzing specific files]
Appendices
- Configuration keys and roles are defined in Settings, including chains, RPC endpoints, secrets, and webhook parameters.
- Payment model enumerations define lifecycle states and support persistence.
Section sources - app/core/config.py - app/db/models/payment.py