An event-driven account management system utilising Chronicle libraries for high-performance and low-latency operations. This project demonstrates a reference architecture for creating accounts, performing transfers, and capturing checkpoints of system state, supported by comprehensive testing and benchmarking tools.
This project showcases a low-latency, high-performance account management microservice accessible via TCP or shared memory. Each inbound and outbound message is persisted in a Chronicle Queue, ensuring durability and traceability. The system is designed to produce minimal garbage and reduce latency jitter, making it suitable for high-frequency financial operations.
Built on Chronicle Queue and leveraging YAML-based input data, this service uses a fluent interface pattern to handle various account operations, such as account creation, fund transfers, and state checkpoints. It also includes benchmarking and testing utilities for performance evaluation.
- Low Latency
-
Designed for microsecond-level response times, especially when using shared memory transport.
- High Throughput
-
Capable of handling high transaction volumes with minimal garbage production.
- Fault Tolerance & Durability
-
All messages persist in a Chronicle Queue, aiding recovery and auditability.
- Flexible Communication
-
Supports TCP and shared memory transport configurations.
- Modular Design
-
Separates API, DTOs, and implementations for easier maintenance and extension.
-
Account Creation: Create new accounts with specified details (name, account ID, currency, initial balance, and optional overdraft).
-
Fund Transfers: Transfer funds between accounts, validating currencies, balances, and overdrafts. Produces events indicating success or failure.
-
Checkpointing: Emit a snapshot of all accounts' current state at a given time for auditing or recovery.
The service can operate in various configurations:
- TCP Client to TCP Service
-
Offers the most flexibility for distributed deployments, with typical latencies under 20 µs.
- TCP Client to Shared Memory Service
-
Achieves better performance, typically ~10 µs latency.
- Shared Memory Client and Service
-
This method achieves the lowest latency, around 1.5 µs for typical operations.
Chronicle Services' channels package and Chronicle Queue facilitate fast, low-overhead message passing.
State changes are persistently recorded for replay or state restoration.
The system is composed of the following layers:
- Input/Output Layer
-
Handles external requests (e.g., through Chronicle Channels) and emits responses/events.
- Service Layer
-
Contains domain services (e.g.,
AccountService) that enforce business rules like account creation, validation, currency checks, and balance updates. - DTOs (Data Transfer Objects)
-
Represent business events such as
CreateAccount,Transfer,OnTransfer, etc. DTOs are validated before processing to ensure data integrity. - Event-Driven Model
-
Uses Chronicle’s method readers/writers and queues to handle requests asynchronously and at low latency.
- Benchmarking and Testing Tools
-
Includes JLBH for microsecond-level latency testing and YAML-driven scenario tests.
-
AccountManagerImpl: Orchestrates incoming events, delegating domain logic toAccountServiceand communicating results back viaAccountManagerOut. -
AccountService: Encapsulates the domain logic for account management (e.g., validating accounts, performing transfers). -
DTO Classes (CreateAccount, Transfer, OnTransfer, etc.): Define the structure and validation rules for events. -
BenchmarkMain Classes: Run performance tests and measure end-to-end latencies under various throughput settings.
A typical flow:
1. A request (e.g., createAccount) arrives via a Chronicle channel.
2. AccountManagerImpl receives the request, delegates validation and business logic to AccountService.
3. AccountService returns success or a failure reason.
4. AccountManagerImpl emits corresponding events (e.g., onCreateAccount or createAccountFailed).
To further enhance this solution for production environments, consider using Chronicle Services. Chronicle Services provides advanced features to ensure high availability, resilience, and smooth operations, including:
- Failover Support
-
If the primary fails, the system automatically switches to a standby service instance, minimizing downtime.
- Process Restarting
-
Automatically restart services after unexpected terminations or during scheduled maintenance windows.
- Live Upgrades
-
Deploy updates without halting the entire system, allowing for rolling upgrades with minimal latency impact.
- Idempotent Collections
-
Simplify restartable event processing by ensuring operations can be safely replayed without side effects.
- Acknowledged Replication
-
Achieve high availability and real-time distribution across hosts, ensuring data consistency and durability.
- Encryption
-
Protect sensitive transaction data with on-disk encryption.
- Monitoring & Management
-
Gain insights into system performance and health, enabling proactive troubleshooting and optimization.
Integrating Chronicle Services allows your account management microservice to meet strict service-level agreements (SLAs), maintain continuous uptime, and adapt to evolving business and technical requirements.
Consider running the service on a server with:
- Modern, Fast CPU
-
Ensure sufficient but not excessive CPU cores.
- High-Performance Storage (NVMe)
-
Reduces jitter from IO operations.
- Fast Memory
-
Memory-intensive operations benefit from faster RAM.
- Small Heap Size
-
A modest heap (e.g., 2 GB) is sufficient since most data is off-heap.
- Tuned Eden Space
-
A large Eden space can reduce GC frequency. For modest volumes, ~1 GB Eden size may suffice.
The client and the service can be laid out in various ways without changing the code.
Client Side |
Service Side |
Typical Latency |
99%ile latency |
TCP |
TCP |
20 µs |
25 µs |
TCP |
Shared Memory |
10 µs |
13 µs |
Shared Memory |
Shared Memory |
1.5 µs |
3 µs |
This approach offers the most distributed option. The typical latencies are under 20 µs with the 99%ile latency not much higher.
sequenceDiagram
autonumber
participant Client as Client Application
participant Gateway as Gateway<br/> (TCP <-> Shared Memory)
participant Service as Service<br/> (Account Manager)
Client->>Gateway: transfer (via TCP)
Note over Client,Gateway: The request is persisted<br/>to a Chronicle Queue
Gateway->>+Service: transfer (via TCP)
Note right of Gateway: Gateway acts as a bridge<br/>and persists the message
Note right of Service: Service processes the event<br/>(update balances, etc.)
Service->>-Gateway: onTransfer (via TCP)
Note over Service,Gateway: Response is persisted again<br/> for audit and recovery
Gateway->>Client: onTransfer (via TCP)
Note left of Gateway: Client receives<br/> the result of the operation
This can be benchmarked all-in-one with the command line properties -Durl=tcp://localhost:1248 -DserviceUrl=tcp://:1248 running AccountManagerBenchmarkMain
This approach offers the most distributed option. The typical latencies are around 10 µs with the 99%ile latency not much higher.
sequenceDiagram
autonumber
participant Client as Client Application
participant Gateway as Gateway<br/>(TCP <-> Shared Memory)
participant Service as Service<br/> (Account Manager)
Client->>Gateway: transfer (via TCP)
Note over Client,Gateway: The request is persisted<br/> to a Chronicle Queue on the Gateway.
Gateway->>+Service: transfer (via Shared Memory)
Note right of Gateway: Gateway provides low-latency<br/> shared memory messaging
Note right of Service: Service processes the event<br/> (e.g., debit & credit accounts)
Service->>-Gateway: onTransfer (via Shared Memory)
Note over Service,Gateway: Response is persisted again<br/> for audit and recovery
Gateway->>Client: onTransfer (via TCP)
Note left of Gateway: Client receives the result<br/> of the transfer operation
This can be benchmarked all-in-one with the command line properties -Durl=tcp://:1248 running AccountManagerBenchmarkMain
This approach offers the most distributed option. The typical latencies are under 2 µs with the 99%ile latency about double this.
sequenceDiagram
autonumber
participant Client as Client Application
participant Queue as Queue<br/> (Shared Memory Channel)
participant Service as Service<br/> (Account Manager)
Client->>Queue: transfer (via Shared Memory)
Note over Client,Queue: The transfer request is immediately<br/> persisted in a Chronicle Queue<br/> for audit and recovery.
Queue->>+Service: transfer (via Shared Memory)
Note right of Queue: The Service reads the request<br/> directly from the Queue.
Note right of Service: The Service processes the event<br/> (e.g., adjust balances).
Service->>-Queue: onTransfer (via Shared Memory)
Note over Service,Queue: The response event is<br/> also persisted in the Queue.
Queue->>Client: onTransfer (via Shared Memory)
Note left of Queue: The Client reads the response,<br/> completing the round-trip<br/> with minimal latency.
This can be benchmarked all-in-one with the default command line properties running AccountManagerBenchmarkMain
We lay out our packages in the following manner
-
api– Input and output interfaces defining the service contract. -
dto– Data Transfer Objects (POJOs) representing commands and events. -
impl– The core implementation of the account management logic. -
util– Utility classes for low-level operations and helpers.
For demos, we might include main classes; however, for a production system, we use a framework Chronicle Services to handle manageability, monitoring, restart and failover.
-
Compile the source files using your preferred Java compiler.
-
AccountManagerServiceMainruns the end service responsible for holding the state and generating results of transactions . -
AccountManagerGatewayMainacts as a gateway listening for TCP connections and writing to/reading from the shared memory queue the microservices uses -
AccountManagerClientMaininjects a few simple messages and waits for resulting events from the AccountManagerImpl
AccountManagerBenchmarkMain runs a JLBH benchmark to report on the latency distribution running on your machine.
Input data is given in YAML format. Here’s an example of an account creation:
# Creating an account for Alice with 1000 EUR
createAccount: {
sender: gw1,
target: vault,
sendingTime: 2023-01-20T10:00:00,
name: alice,
account: 101013,
currency: EUR,
balance: 1000
}And an example of a transfer:
# Alice sends 10 EUR to Bob
transfer: {
sender: gw2,
target: vault,
sendingTime: 2023-01-20T10:03:00,
from: 101013,
to: 101025,
currency: EUR,
amount: 10,
reference: Dog food
}This demo does not handle all edge cases and lacks a user-friendly interface. Future enhancements may include:
-
Improved error handling
-
Better user interfaces or REST/HTTP endpoints
-
More robust fault tolerance and recovery strategies, possibly leveraging Chronicle Services further
