diff --git a/.gitignore b/.gitignore
index 811b518..050b750 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
+# Temporarily exclude these files from aide to make the token size manageable
+#account/
+#order-processor/
+
### How to update
# This is copied from OpenHFT/.gitignore
# update the original and run OpenHFT/update_gitignore.sh
diff --git a/README.adoc b/README.adoc
index 0754899..1775817 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,12 +1,57 @@
-= Chronicle-Queue-Demo
-Peter Lawrey
-:imagesdir: images
+= Chronicle Queue Demo - README
+:toc:
+:toclevels: 3
-This is a tutorial demonstrating the usage of Chronicle Queue with simple demo programs.
+This repository demonstrates the usage of Chronicle Queue (and related Chronicle libraries) through multiple examples, including an order processor, event routing, and more. Below is a quick start and pointers to further documentation.
+
+== Quick Start
+
+.Basic Steps
+----
+git clone https://github.com/OpenHFT/Chronicle-Queue-Demo.git
+cd Chronicle-Queue-Demo
+mvn clean install
+----
+
+To run a simple example, like the hello-world module:
+
+----
+cd hello-world
+mvn install exec:java@RecordInputToConsoleMain
+----
+
+== Documentation
+
+The project’s documentation is consolidated in five AsciiDoc files:
+
+1. xref:architecture.adoc[Architecture]
+2. xref:usage-and-tests.adoc[Usage & Tests]
+3. xref:reference.adoc[Reference, Style & Glossary]
+4. xref:account/README.adoc[Account Management System (AMS)]
+5. xref:order-processor/README.adoc[Order Processor]
+
+Refer to them for details on architecture, usage instructions, testing approaches, style guides, and advanced references.
+
+== Repository Overview
+
+The modules in this repository include:
+
+* **account**: An Account Management System (AMS) example using Chronicle Queue event-driven logic.
+* **benchmarks**: Scripts and classes demonstrating throughput/latency benchmarks (e.g., LatencyDistributionMain, ThroughputMain).
+* **event-routing**: Showcases how messages can be routed via Chronicle Queues with interfaces like `ViaIn`, `ViaOut`.
+* **hello-world**: A simple introduction to an event-driven microservice using Chronicle Queue (input, exclamation addition, output).
+* **md-pipeline**: A Market Data pipeline example with aggregator, strategy, and an exchange simulator.
+* **message-history-demo**: Demonstrates Chronicle’s `MessageHistory` in bridging or event processing scenarios.
+* **messages-with-text**: An example of writing/reading textual content with minimal garbage creation.
+* **order-processor**: A submodule that acts as a simple OMS (Order Management System), referencing FIX 4.2 concepts.
+
+For deeper details on each, see xref:architecture.adoc[Architecture].
== Order Processor
-image::Two-hop-latency.PNG[]
+This is a tutorial demonstrating the usage of Chronicle Queue with simple demo programs.
+
+image::images/Two-hop-latency.png[]
You can find the source code for the order processor example https://github.com/OpenHFT/Chronicle-Queue-Demo/tree/master/order-processor[here].
@@ -18,13 +63,13 @@ This allows you to install Linux packages that aren't already on Windows.
When this asks you which packages you want to install, search for and add `git`.
This is under `Development` and you need to click `skip` so it says to `install`.
-image::gitpack.png[]
+image::images/gitpack.png[]
Open IntelliJ or your favorite https://en.wikipedia.org/wiki/Integrated_development_environment[Integrated Development Environment (IDE)]. If you haven't installed an IDE, we recommend https://www.jetbrains.com/idea/download/#section=windows[IntelliJ], which we'll use for this tutorial.
In IntelliJ, select `Get from VCS` to clone the Chronicle Queue Sample code.
-image::homegit.png[]
+image::images/homegit.png[]
Then, copy the following URL into the `Git Repository URL` field and remember the `Parent Directory`. Click `Clone` to get the code.
@@ -33,11 +78,11 @@ Then, copy the following URL into the `Git Repository URL` field and remember th
https://github.com/OpenHFT/Chronicle-Queue-Demo.git
----
-image::Clone.PNG[]
+image::images/Clone.png[]
If you close the project, you can reopen it by going to `File -> Open`. You'll find the repository in the directory where you saved it.
-image::directory.PNG[]
+image::images/directory.png[]
Now you're ready to run the example programs! You can start with https://github.com/OpenHFT/Chronicle-Queue-Demo/tree/master/simple-input[Simple Input].
diff --git a/account.ad b/account.ad
new file mode 100644
index 0000000..0ebbea3
--- /dev/null
+++ b/account.ad
@@ -0,0 +1,63 @@
+= Project Summary
+
+== Overview
+
+The project is an Account Management System (AMS) leveraging the Chronicle libraries for high-performance, low-latency event-driven operations. It emphasizes documentation-driven development with AI integration for streamlined engineering. The AMS manages account creation, transfers, and state snapshots through a comprehensive suite of YAML-based tests, detailed functional requirements, and a modular architecture.
+
+== Key Features
+
+* **Low-Latency and High-Throughput**: Designed for operations with microsecond-level latencies and over 1M messages per second throughput.
+* **Event-Driven Design**: Utilizes Chronicle Queue for durable message persistence and asynchronous operations.
+* **Rich Documentation**: Implements AsciiDoc for capturing requirements, workflows, and style guides to align development with AI tools.
+* **Testing Framework**: YAML-driven scenarios test various operations, including edge cases and invalid inputs, ensuring robust functionality.
+
+== Functional Components
+
+=== Documentation
+* **AIDE Glossary**: Defines project-specific terminology, such as AIDE (Artificial Intelligence Development Environment) and tokens/line ratios.
+* **Style Guide**: Enforces British English conventions and standardizes coding/documentation practices.
+* **Workflow**: Outlines iterative development—document, test, code, review, and repeat—keeping code and requirements synchronized.
+
+=== Code
+* **Core Classes**:
+* `AccountManagerImpl`: Orchestrates incoming events and delegates logic to `AccountService`.
+* `AccountService`: Validates accounts, manages balances, and processes transfers.
+* DTOs (e.g., `CreateAccount`, `Transfer`): Represent operations with fluent setter methods for chaining.
+* **Utilities**:
+* `LogsAccountManagerOut`: A mock implementation for testing event outputs.
+* `ErrorListener`: Handles JVM-level errors gracefully.
+
+=== Testing
+* **Parameterized Tests**: YAML scenarios validate the AMS against predefined inputs and outputs.
+* **Coverage**:
+* Account creation, transfers, checkpoints.
+* Edge cases, including invalid currencies, missing fields, and insufficient funds.
+* Performance benchmarks using JLBH for latency testing.
+
+=== Benchmark Results
+Demonstrated latencies:
+
+* Shared Memory: ~1.5 µs
+* TCP: ~20 µs
+* End-to-End Variance: Minimal under high load conditions.
+
+== Directories and Files
+
+All directories here are under `account` directory
+
+=== Code
+* `src/main/java`: Implementation of AMS.
+* `AccountManagerImpl.java`: Main orchestration logic.
+* `AccountService.java`: Domain operations.
+
+=== Tests
+* `src/test/java`: Unit and integration tests.
+* YAML files: Input (`in.yaml`), expected output (`out.yaml`), and setup (`_setup.yaml`) configurations.
+
+=== Utilities
+* `list_files_asciidoc.sh`: Script to generate directory content summaries.
+* `benchmark-results.txt`: Performance insights.
+
+== Summary
+
+The AMS is a modular, high-performance system underpinned by comprehensive documentation, rigorous testing, and cutting-edge Chronicle technologies. This structure ensures maintainability, scalability, and alignment with evolving requirements.
diff --git a/account/README.adoc b/account/README.adoc
index 12dae6f..a72e0d1 100644
--- a/account/README.adoc
+++ b/account/README.adoc
@@ -1,68 +1,111 @@
= Low-Latency Account Management System Over TCP and Shared Memory
-Peter Lawrey
+:author: Peter Lawrey
+:revdate: 2024-12-16
+:revnumber: 1.3
+:toc: left
+:toclevels: 3
-This example shows a simple high performance microservice accessible via TCP or shared memory storing each message in and out persisted in a Chronicle Queue.
-There are a number of configurations with a comparison of the performance.
-It is designed to produce minimal garbage to minimise jitter.
+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 Account Management System is designed to handle basic account operations such as account creation and fund transfers.
-It's built on Chronicle Wire and uses Fluent Interface pattern, taking advantage of YAML files for test input data.
+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.
-This system leverages the `channels` package in Chronicle Wire, a high-speed messaging framework that excels in handling high volume and latency-critical data.
+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.
-== Overview
+== Key Features
-The Account Management System serves as an efficient banking system that facilitates basic account operations, including account creation and fund transfers.
-It is architected for low latency, efficient memory utilization, and performance under high transaction volume scenarios.
+* **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.
-The core functionality is built around three main components:
+== System Overview
-. *Account Creation*: This feature allows for the establishment of new bank accounts with relevant details, such as account holder's name, account ID, currency type, and the initial balance.
-. *Fund Transfer*: The system is capable of processing funds transfer between different accounts securely and swiftly.
-Transfer details include sender account, target account, transfer amount, currency type, and a reference note.
-. *Checkpointing*: The checkpoint functionality provides a snapshot of the current state of all accounts in the system at a specific point in time.
+=== Core Operations
-=== Network Communication
+. **Account Creation**:
+Create new accounts with specified details (name, account ID, currency, initial balance, and optional overdraft).
-The system communicates over TCP to ensure reliable data transmission, or shared memory where possible to minimise latencies.
-TCP is chosen for its built-in error checking and correction, ensuring that all account management data reaches the correct destination in the correct order and without errors.
-All shared memory messaging is persisted to memory mapped files as it is written.
+. **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.
+
+=== Architecture and Communication
+
+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.
+
+== Architecture and Key Components
+
+=== Overview
+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.
+
+=== Key Classes
+* `AccountManagerImpl`: Orchestrates incoming events, delegating domain logic to `AccountService` and communicating results back via `AccountManagerOut`.
+* `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`).
=== Testability and Benchmarking
-The system also comes with a comprehensive set of tests and a benchmarking suite, allowing for easy performance measurement and regression testing.
-The `AccountManagerBenchmarkMain` class acts as a benchmarking and testing entry point, demonstrating the functionality of the system and showing the execution time for the operations.
+A suite of tests and benchmarks (e.g., `AccountManagerBenchmarkMain`) allows easy verification of correctness and performance.
+You can measure latency, throughput, and other key metrics to ensure the service meets performance goals.
-=== Using a Production Server
+== Making It Production-Ready with Chronicle Services
-In production, you want a server that has
+To further enhance this solution for production environments, consider using https://chronicle.software/services/[Chronicle Services].
+Chronicle Services provides advanced features to ensure high availability, resilience, and smooth operations, including:
-- A recent, fast processor with enough cores.
-More cores isn't always better esp if it's more than you need.
-- A Fast enterprise grade storage device e.g. NVMe.
-This can reduce jitter due to writes by a factor fo 10 or better.
-- Fast memory.
-As event processing systems stream a large amount of data, yet retain relatively little, the memory size doesn't need to be large if your IO subsystem is fast enough.
-- As we advocate producing little garbage and storing most data off heap, your heap size might be relatively small. e.g. 2 GB
-- If you have a large eden space of 24 GB, you can produce 1 GB/hour of garbage and only minor collect once per day.
-Possibly in a maintenance window if you have one.
-However, for modest volumes an Eden size of 1 GB might be enough.
+* **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.
-=== Commercial Extensions to Productionise Microservices
+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.
-There is a commercial extension https://chronicle.software/services/ that supports
+image::img/Chronicle-Services-Diagram.png[]
-- Control over restarting of services
-- Idempotent collections to simplify restarting event processing.
-- Acknowledged Replication for High Availability and Realtime Distribution restartable across hosts.
-- Encryption of messages stored on disk.
-- Monitoring and Management support
+== Recommended Production Environment
-image:img/Chronicle-Services-Diagram.png[]
+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.
== Network Layouts
-The client and the service can be laid out in a variety of ways without changing the code.
+The client and the service can be laid out in various ways without changing the code.
.Ballpark latencies running on a production quality server
|===
@@ -72,7 +115,7 @@ The client and the service can be laid out in a variety of ways without changing
| Shared Memory | Shared Memory | 1.5 µs | 3 µs
|===
-=== TCP Client, TCP Service
+=== Example: TCP Client, TCP Service
This approach offers the most distributed option.
The typical latencies are under 20 µs with the 99%ile latency not much higher.
@@ -80,58 +123,85 @@ The typical latencies are under 20 µs with the 99%ile latency not much hig
[source,mermaid]
....
sequenceDiagram
-autonumber
-Client->>Gateway: transfer
-Note over Client,Gateway: via TCP, persisted
-Gateway->>+Service: transfer
-Note right of Gateway: via TCP
-Note right of Service: processes event
-Service->>-Gateway: onTransfer
-Note over Service,Gateway: via TCP, persisted
-Gateway->>Client: onTransfer
-Note left of Gateway: via TCP
+ autonumber
+
+ participant Client as Client Application
+ participant Gateway as Gateway (TCP <-> Shared Memory)
+ participant Service as Service (Account Manager)
+
+ Client->>Gateway: transfer (via TCP)
+ Note over Client,Gateway: The request is persisted to a Chronicle Queue
+
+ Gateway->>+Service: transfer (via TCP)
+ Note right of Gateway: Gateway acts as a bridge and persists the message
+ Note right of Service: Service processes the event (update balances, etc.)
+
+ Service->>-Gateway: onTransfer (via TCP)
+ Note over Service,Gateway: Response is persisted again for audit and recovery
+
+ Gateway->>Client: onTransfer (via TCP)
+ Note left of Gateway: Client receives 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`
-=== TCP Client, Shared Memory Service
+=== Example: TCP Client, Shared Memory Service
-This approach offers the most distributed option. The typical latencies are around 10 µs with the 99%ile latency not much higher.
+This approach offers the most distributed option.
+The typical latencies are around 10 µs with the 99%ile latency not much higher.
[source,mermaid]
....
sequenceDiagram
-autonumber
-Client->>Gateway: transfer
-Note over Client,Gateway: via TCP, persisted
-Gateway->>+Service: transfer
-Note right of Gateway: via Shared Memory
-Note right of Service: processes event
-Service->>-Gateway: onTransfer
-Note over Service,Gateway: via Shared Memory, persisted
-Gateway->>Client: onTransfer
-Note left of Gateway: via TCP
+ autonumber
+
+ participant Client as Client Application
+ participant Gateway as Gateway (TCP <-> Shared Memory)
+ participant Service as Service (Account Manager)
+
+ Client->>Gateway: transfer (via TCP)
+ Note over Client,Gateway: The request is persisted to a Chronicle Queue on the Gateway.
+
+ Gateway->>+Service: transfer (via Shared Memory)
+ Note right of Gateway: Gateway provides low-latency shared memory messaging
+ Note right of Service: Service processes the event (e.g., debit & credit accounts)
+
+ Service->>-Gateway: onTransfer (via Shared Memory)
+ Note over Service,Gateway: Response is persisted again for audit and recovery
+
+ Gateway->>Client: onTransfer (via TCP)
+ Note left of Gateway: Client receives the result of the transfer operation
....
This can be benchmarked all-in-one with the command line properties `-Durl=tcp://:1248` running `AccountManagerBenchmarkMain`
-=== Shared Memory Client and Service
+=== Example: Shared Memory Client and Service
-This approach offers the most distributed option. The typical latencies are under 2 µs with the 99%ile latency about double this.
+This approach offers the most distributed option.
+The typical latencies are under 2 µs with the 99%ile latency about double this.
[source,mermaid]
....
sequenceDiagram
-autonumber
-Client->>queue: transfer
-Note over Client,queue: via Shared Memory, persisted
-queue->>+Service: transfer
-Note right of queue: via Shared Memory
-Note right of Service: processes event
-Service->>-queue: onTransfer
-Note over Service,queue: via Shared Memory, persisted
-queue->>Client: onTransfer
-Note left of queue: via Shared Memory
+ autonumber
+
+ participant Client as Client Application
+ participant Queue as Queue (Shared Memory Channel)
+ participant Service as Service (Account Manager)
+
+ Client->>Queue: transfer (via Shared Memory)
+ Note over Client,Queue: The transfer request is immediately persisted in a Chronicle Queue for audit and recovery.
+
+ Queue->>+Service: transfer (via Shared Memory)
+ Note right of Queue: The Service reads the request directly from the Queue.
+ Note right of Service: The Service processes the event (e.g., adjust balances).
+
+ Service->>-Queue: onTransfer (via Shared Memory)
+ Note over Service,Queue: The response event is also persisted in the Queue.
+
+ Queue->>Client: onTransfer (via Shared Memory)
+ Note left of Queue: The Client reads the response, completing the round-trip with minimal latency.
+
....
This can be benchmarked all-in-one with the default command line properties running `AccountManagerBenchmarkMain`
@@ -140,19 +210,18 @@ This can be benchmarked all-in-one with the default command line properties runn
We lay out our packages in the following manner
-- `api` package for the input and output interfaces.
-The input of one microservice might be the output of another microservice.
-- `dto` package for POJOs (Plain Old Java Objects) that hold the data associated with each event.
-- `impl` package for the service implementation and the high level classes it uses
-- `util` package for separating low level helper methods and classes.
+* `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 https://chronicle.software/services/ to handle manageability, monitoring, restart and fail over.
+For demos, we might include `main` classes; however, for a production system, we use a framework https://chronicle.software/services/[Chronicle Services] to handle manageability, monitoring, restart and failover.
== How to Run
. Compile the source files using your preferred Java compiler.
-. `AccountManagerServiceMain` runs the end service responsible for holding state and generating results of transactions
-
+. `AccountManagerServiceMain` runs the end service responsible for holding the state and generating results of transactions
+.
. `AccountManagerGatewayMain` acts as a gateway listening for TCP connections and writing to/reading from the shared memory queue the microservices uses
. `AccountManagerClientMain` injects a few simple messages and waits for resulting events from the AccountManagerImpl
@@ -199,13 +268,11 @@ transfer: {
}
----
-== Note
-
-This is a basic implementation and does not handle many edge cases.
-It also lacks a user-friendly interface, and the input is provided directly through YAML files.
-It's intended as a demonstration of a system built on Chronicle Wire, and may not be suitable for production use without further modifications and improvements.
+== Limitations and Future Work
-== Contribute
+This demo does not handle all edge cases and lacks a user-friendly interface.
+Future enhancements may include:
-We would love your contributions!
-Please submit a pull request with any improvements or bug fixes you have made.
+* Improved error handling
+* Better user interfaces or REST/HTTP endpoints
+* More robust fault tolerance and recovery strategies, possibly leveraging Chronicle Services further
diff --git a/account/pom.xml b/account/pom.xml
index 51b3eb6..ccffd7d 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -54,5 +54,41 @@
4.11.0test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.10.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.10.0
+ test
+
+
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M9
+
+ false
+
+
+
+
diff --git a/account/prompts/improve-code.adoc b/account/prompts/improve-code.adoc
new file mode 100644
index 0000000..e79548d
--- /dev/null
+++ b/account/prompts/improve-code.adoc
@@ -0,0 +1,72 @@
+= Developer Prompt: Improving Classes for AMS
+
+:author: Peter Lawrey
+:revdate: 2024-12-16
+:revnumber: 1.0
+:doctype: book
+:toc: left
+:toclevels: 3
+
+You are given a set of requirements and an existing implementation of an Account Management Service (AMS).
+The AMS processes account-related commands (events) such as account creation, fund transfers, and checkpoint requests, and produces corresponding success/failure events.
+The code currently meets the basic functional requirements but can be improved for clarity, maintainability, and robustness.
+
+Your task is to improve selected classes in the codebase.
+Consider the following options and guidelines:
+
+== Guidelines
+
+1. **Adhere to the Provided Requirements**:
+The code must continue to fulfill the requirements specified in the `Account Management Service Requirements` document.
+Any changes should not break the contract defined there.
+
+2. **Validation and Error Handling**:
+Assume that the framework validates that DTOs (Data Transfer Objects) before use.
+Add a comment showing where validation would otherwise occur.
+If a command is missing critical fields or contains invalid values, handle it gracefully by producing failure events rather than exceptions visible to callers.
+
+3. **Time Management**:
+All events should use `SystemTimeProvider.CLOCK.currentTimeNanos()` to set their `sendingTime` fields, ensuring nanosecond-precision wall clock timestamps.
+
+4. **Logging and Comments**:
+Add meaningful comments where appropriate to explain the rationale behind certain decisions, especially if the code deviates from typical patterns.
+Consider using `Jvm.debug()`, `Jvm.warn()`, and `Jvm.error()` for logging.
+Comments should clarify non-obvious logic, error handling decisions, or performance trade-offs.
+Do not add comments for trivial logic.
+
+5. **Fluent Interfaces and Deep Copies**:
+Preserve the fluent interface style for DTO setters to allow method chaining.
+When storing new accounts, ensure that `CreateAccount` objects are deep copied before saving them to the internal map, as per the requirements.
+
+6. **Checkpoints and State Serialization**:
+During checkpoint processing, ensure that all currently known accounts are emitted as `onCreateAccount` events.
+Consider how to handle any edge cases (e.g., empty account lists).
+
+7. **Readability and Maintainability**:
+Consider extracting common logic (e.g., target checks, currency checks, funds checks) into separate helper methods to reduce code repetition.
+Make sure your class-level and method-level documentation provides a clear picture of what the code does, why, and how it aligns with the requirements.
+
+== Options to Consider
+
+* Add Javadoc to all classes and their public methods, describing the class’s role, its main responsibilities, and linking it back to the requirements.
+* Introduce private helper methods to streamline complex validation or repetitive tasks.
+* Use descriptive variable and method names to enhance clarity.
+* Check that all failure events include a meaningful `reason` field that matches the requirements.
+* Consider adding `@Override` annotations, if missing, to clarify implemented methods from interfaces.
+* Add informative comments that explain why certain validations or steps are necessary, rather than just stating what the code does.
+* Ensure that the codebase is consistent in its style and adheres to the project’s coding standards.
+* Consider how to handle edge cases and exceptional conditions, ensuring that the code behaves predictably and correctly in all scenarios.
+
+== Deliverables
+
+Improve the existing codebase by addressing the guidelines and options provided.
+Submit the updated classes with the changes you have made, along with a brief summary of the modifications you implemented and why you chose to make them.
+
+== Objective
+
+By following the above guidelines and considering the options, improve the existing codebase to be more robust, understandable, and aligned with the specified requirements.
+The resulting classes should present a clean, well-documented, and maintainable code structure that clearly communicates their purpose and logic.
+
+== Code To Improve
+
+Find the code to improve below:
diff --git a/account/prompts/improve-test-data.adoc b/account/prompts/improve-test-data.adoc
new file mode 100644
index 0000000..26ac9ba
--- /dev/null
+++ b/account/prompts/improve-test-data.adoc
@@ -0,0 +1,159 @@
+= Developer Prompt: Improving Test Cases for AMS
+
+Your task is to enhance an existing test configuration that utilizes YAML files for initializing system state (`_setup.yaml`), specifying input commands (`in.yaml`), and verifying expected outcomes (`out.yaml`).
+The goal is to produce maintainable, clear, and requirements-aligned test cases.
+
+== Overview
+
+The testing approach involves the following files:
+
+* `_setup.yaml` - Initialises the system state before the test scenario begins (e.g., creating initial accounts).
+* `in.yaml` - Defines the input commands (events) that the system under test will process.
+* `out.yaml` - Specifies the expected events produced by the system in response to the inputs, along with helpful comments that link the outputs back to the corresponding input events.
+
+Below is an illustrative structure:
+
+.Setup (`_setup.yaml`)
+----
+---
+# Create account for Alice (account #101013) starting with 15 EUR.
+# Rationale: This sets up a baseline account state for subsequent operations.
+createAccount: {
+ sender: gw1,
+ target: vault,
+ sendingTime: 2023-01-20T10:00:00,
+ name: alice,
+ account: 101013,
+ currency: EUR,
+ balance: 15
+}
+...
+----
+
+.Input (`in.yaml`)
+----
+---
+# Transfer 10 EUR from Alice (101013) to Bob (101025).
+# Scenario: This should succeed if Bob’s account is in EUR and both accounts are valid.
+transfer: {
+ sender: gw2,
+ target: vault,
+ sendingTime: 2023-01-20T10:03:00,
+ from: 101013,
+ to: 101025,
+ currency: EUR,
+ amount: 10,
+ reference: Dog food
+}
+...
+---
+# This operation requests a checkpoint.
+# Checkpoints are typically used to dump or save the state of the system at a certain point in time.
+# In this case, it will dump all the accounts.
+checkPoint: {
+ sender: gw2,
+ target: vault,
+ sendingTime: 2023-01-20T11:00:00,
+}
+...
+----
+
+[source,mermaid]
+----
+sequenceDiagram
+ participant SetupFile as _setup.yaml
+ participant InputFile as in.yaml
+ participant TestRunner as YamlTester
+ participant System as System Under Test (AMS)
+ participant ExpectedOutput as out.yaml
+
+ SetupFile->>TestRunner: Load initial state instructions
+ Note over TestRunner: The Test Runner reads _setup.yaml and applies initial configurations (e.g., create accounts)
+
+ TestRunner->>System: Initialise system state from _setup.yaml
+ Note over System: System now has initial state (e.g., Alice’s account with 15 EUR)
+
+ InputFile->>TestRunner: Provide input commands/events
+ Note over TestRunner: The Test Runner reads in.yaml which includes operations like transfers, checkpoints
+
+ TestRunner->>System: Replay input events from in.yaml
+ Note over System: System processes each command and produces corresponding output events (e.g., onTransfer, createAccountFailed)
+
+ System->>TestRunner: Return produced events
+ Note over TestRunner: The Test Runner captures all events generated by the system in response to input
+
+ ExpectedOutput->>TestRunner: Provide expected events (out.yaml)
+ Note over TestRunner: The Test Runner checks the produced events against the expected output defined in out.yaml
+
+ TestRunner->>TestRunner: Compare actual vs expected events
+ alt All Events Match
+ TestRunner->>System: Test PASSED
+ else Some Events Differ
+ TestRunner->>System: Test FAILED
+ end
+----
+
+== Guidelines
+
+1. **Clarity and Context**:
+Add descriptive comments to `_setup.yaml` and `in.yaml` to explain each operation’s intent.
+In `out.yaml`, reference the input event that caused the output.
+This makes it easier for other developers to understand the test scenarios at a glance.
+
+2. **Time Management**:
+Document that real-time tests should use `SystemTimeProvider.CLOCK.currentTimeNanos()` for `sendingTime`.
+Though test files may use fixed timestamps, emphasize in comments that production environments rely on `SystemTimeProvider` for consistent, nanosecond-precision timestamps.
+
+3. **Validation Checks**:
+Introduce failure scenarios:
+* A `createAccount` command with an invalid balance (e.g., negative balance) to produce `createAccountFailed`.
+* A `transfer` from a non-existent account or with insufficient funds to produce `transferFailed`.
+
+ In `in.yaml`, comment these scenarios and in `out.yaml`, show the expected failure outputs, including a `reason` field that aligns with the system’s requirements.
+
+4. **Reusability and Maintenance**:
+If your setup becomes complex, consider YAML anchors, aliases, or splitting large scenarios into multiple files.
+Add comments linking tricky scenarios to relevant sections of the requirements document, ensuring future maintainers understand the rationale behind each test.
+
+5. **Coverage**:
+Include scenarios that cover:
+* Multiple successful account creations and transfers.
+* At least one invalid `createAccount` scenario.
+* At least one invalid `transfer` scenario.
+* A `checkPoint` command to verify the sequence of `startCheckpoint`, `onCreateAccount` for each known account, and `endCheckpoint` events.
+
+6. **Naming and Organization**:
+Use meaningful and specific operation descriptions.
+Instead of generic comments, specify the exact accounts, currencies, and reasons.
+Label scenarios (e.g., "Scenario: Insufficient Funds Transfer") to quickly identify their purpose.
+
+== Sections for Setup and Input Data
+
+- **Setup Section (`_setup.yaml`)**:
+Place all initial state operations here.
+Add comments that justify these initial states and their relevance to the upcoming tests.
+
+----
+# Example (in `_setup.yaml`):
+# Creating initial accounts to ensure subsequent transfers have valid source and destination accounts.
+createAccount: { ... }
+...
+
+----
+
+- **Input Section (`in.yaml`)**:
+Define the sequence of commands tested.
+Include both normal and edge cases, clearly tagging scenarios for quick reference.
+
+----
+# Example (in `in.yaml`):
+# Scenario: Attempt to transfer from a non-existent account to test transferFailed event.
+transfer: { ... }
+...
+
+----
+
+== Deliverables
+
+Enhance the existing `_setup.yaml` and `in.yaml` files according to the above guidelines.
+Once updated, provide a brief summary of the changes made and the reasons behind them, focusing on improved clarity, test coverage, and alignment with requirements.
diff --git a/account/prompts/requirements.adoc b/account/prompts/requirements.adoc
new file mode 100644
index 0000000..495d36f
--- /dev/null
+++ b/account/prompts/requirements.adoc
@@ -0,0 +1,183 @@
+= Account Management Service Requirements
+
+:author: Peter Lawrey
+:revdate: 2024-12-16
+:revnumber: 1.1
+:doctype: book
+:toc: left
+:toclevels: 3
+
+== Introduction
+
+This document specifies the functional requirements for an Account Management Service (AMS) that processes account-related commands and generates corresponding events.
+The AMS is designed to be driven by incoming commands (events) such as account creation, fund transfers, and checkpoint requests.
+It produces events to indicate success or failure and to provide state snapshots.
+
+== Terminology
+
+*Account*: A financial store of value characterized by an account number, owner name, currency, balance, and an optional overdraft limit.
+
+*Command*: An inbound request to the system (e.g., create an account, transfer funds, request checkpoint).
+Commands are modelled as Events in this system.
+
+*Event*: An outbound notification from the system that indicates a command's state change, success, or failure.
+
+*Checkpoint*: A request to serialize or snapshot the current state of all accounts for audit or recovery purposes.
+
+== Functional Requirements
+
+=== Account Creation
+
+==== Inputs
+
+1. A `CreateAccount` command containing:
+
+* `sender`: The origin of the command.
+* `target`: The intended system (e.g., `vault`).
+* `sendingTime`: Holds when the command was sent as a wall clock timestamp with nanosecond resolution.
+* `name`: The account holder's name.
+* `account`: The numeric identifier for the account (long).
+* `currency`: The currency in which the account operates.
+* `balance`: The initial balance of the account.
+* `overdraft`: The overdraft limit allowed for the account.
+
+The `sendingTime` can be set using `SystemTimeProvider.CLOCK.currentTimeNanos()`.
+
+==== Processing
+
+Upon receiving a `CreateAccount` command:
+
+1. Validate that `target` matches this instance's configured identifier.
+2. Validate that `balance` ≥ 0.
+3. Validate that the `account` number does not already exist in the system.
+4. If validation fails, output a `createAccountFailed` event, including the reason.
+5. If validation succeeds:
+
+* Store the account details in a local data structure (e.g., a `Map`).
+* Emit an `onCreateAccount` event with the stored `CreateAccount` details.
+
+==== Outputs
+
+* `onCreateAccount` event on success:
+* `sender`: System ID (e.g., `vault`)
+* `target`: The original `sender` of the `CreateAccount` command
+* `sendingTime`: The system's current time
+* Embedded `createAccount` field containing the original request data.
+
+* `createAccountFailed` event on failure:
+* `sender`: System ID (e.g., `vault`)
+* `target`: The original `sender` of the `CreateAccount` command
+* `sendingTime`: The system's current time
+* `reason`: A textual description of the failure (e.g., "invalid balance", "account already exists").
+
+=== Transfer Funds
+
+==== Inputs
+
+1. A `Transfer` command containing:
+
+* `sender`: The origin of the command.
+* `target`: The intended system (e.g., `vault`).
+* `sendingTime`: Holds when the command was sent as a wall clock timestamp with nanosecond resolution.
+* `from`: The source account number.
+* `to`: The destination account number.
+* `currency`: The currency of the transfer.
+* `amount`: The amount to transfer.
+* `reference`: A reference field for the reason for the transfer or details.
+
+==== Processing
+
+Upon receiving a `Transfer` command:
+
+1. Validate that `target` matches this instance's identifier.
+2. Verify that the `from` account exists and its currency matches the `Transfer.currency`.
+3. Verify that the `to` account exists and its currency matches the `Transfer.currency`.
+4. Check that the `from` account has sufficient funds (`balance + overdraft ≥ amount`).
+5. If validation fails, emit a `transferFailed` event with an appropriate `reason`.
+6. If valid, update both accounts:
+* Deduct the `amount` from the `from` account's balance.
+* Add `amount` to the `to` account's balance.
+7. Emit an `onTransfer` event indicating success.
+
+==== Outputs
+
+* `onTransfer` event on success:
+* `sender`: System ID (e.g., `vault`)
+* `target`: The original `sender` of the `Transfer` command
+* `sendingTime`: The system's current time
+* Embedded `transfer` field containing the original `Transfer` command data.
+
+* `transferFailed` event on failure:
+* `sender`: System ID (e.g., `vault`)
+* `target`: The original `sender` of the `Transfer` command
+* `sendingTime`: The system's current time
+* Embedded `transfer` field containing the original request
+* `reason`: A textual description of the failure (e.g., "from account doesn't exist", "insufficient funds").
+
+=== Checkpoint
+
+==== Inputs
+
+1. A `CheckPoint` command containing:
+* `sender`: The origin of the command.
+* `target`: The intended system (e.g., `vault`).
+* `sendingTime`: The timestamp of when the command was sent.
+
+==== Processing
+
+Upon receiving a `CheckPoint` command:
+
+1. Validate that `target` matches this instance's identifier.
+2. Emit a `startCheckpoint` event.
+3. For every account currently held in the system:
+* Emit an `onCreateAccount` event representing its current state.
+4. Emit an `endCheckpoint` event.
+
+==== Outputs
+
+* `startCheckpoint` event:
+* `sender`: The original `sender` of the `CheckPoint`
+* `target`: The system ID (e.g., `vault`)
+* `sendingTime`: The system's current time
+
+* A series of `onCreateAccount` events for each known account, reflecting their current state at the time of checkpoint.
+
+* `endCheckpoint` event:
+* `sender`: The original `sender` of the `CheckPoint`
+* `target`: The system ID (e.g., `vault`)
+* `sendingTime`: The system's current time
+
+== Non-Functional Requirements
+
+1. **Performance**: The system should handle account lookups and updates in O(1) average time via efficient data structures (e.g., HashMap or LinkedHashMap).
+
+2. **Concurrency**: The system may assume single-threaded inputs.
+
+3. **Error Handling**: All invalid or unexpected command conditions result in failure events rather than exceptions visible to callers.
+
+4. **Time Management**: `sendingTime` should be based on a reliable system clock.
+
+== Validation and Testing
+
+To verify these requirements:
+
+1. Send a `createAccount` command with valid parameters and ensure `onCreateAccount` is emitted.
+2. Send a `createAccount` command with invalid parameters (e.g., negative balance or duplicate account number) and confirm that `createAccountFailed` is emitted.
+3. Perform a valid `transfer` and ensure `onTransfer` is emitted with the updated balances.
+4. Attempt invalid transfers and ensure `transferFailed` events are emitted.
+5. Issue a `checkPoint` command and validate that `startCheckpoint`, multiple `onCreateAccount` events (one per account), and `endCheckpoint` are produced in order.
+
+== Traceability
+
+Each requirement above directly corresponds to a portion of the Java implementation in `AccountManagerImpl.java`:
+
+* Account creation logic: `createAccount(CreateAccount createAccount)`
+* Transfer logic: `transfer(Transfer transfer)`
+* Checkpoint logic: `checkPoint(CheckPoint checkPoint)`
+
+Events and conditions are explicitly handled in private utility methods (e.g., `sendCreateAccountFailed`, `sendOnCreateAccount`, `sendTransferFailed`, `sendOnTransfer`).
+
+== Conclusion
+
+This document provides a high-level specification of the required functionalities and expected behaviours of the Account Management Service.
+Implementing these requirements should align with the Java code structure and produce consistent events for all supported operations.
diff --git a/account/src/main/java/run/chronicle/account/AccountManagerBenchmarkMain.java b/account/src/main/java/run/chronicle/account/AccountManagerBenchmarkMain.java
index b8ce7f7..c856fc0 100644
--- a/account/src/main/java/run/chronicle/account/AccountManagerBenchmarkMain.java
+++ b/account/src/main/java/run/chronicle/account/AccountManagerBenchmarkMain.java
@@ -25,37 +25,6 @@
import static net.openhft.chronicle.core.time.SystemTimeProvider.CLOCK;
-/*
--Xmx64m -Dthroughput=100000 -DrunTime=30 -Dbuffered=false -Durl=tcp://localhost:1248 -DaccountForCoordinatedOmission=false
--------------------------------- SUMMARY (end to end) us -------------------------------------------
-Percentile run1 run2 run3 run4 run5 % Variation
-50.0: 10.99 10.99 11.02 11.02 10.99 0.19
-90.0: 18.02 17.89 15.70 11.22 11.12 28.86
-99.0: 20.96 19.94 15.86 15.86 15.79 14.89
-99.7: 34.11 21.02 16.11 15.92 15.89 17.73
-99.9: 42.18 22.62 16.67 16.34 16.18 21.00
-99.97: 2021.38 26.08 18.21 17.44 17.25 25.45
-99.99: 5480.45 60.48 44.48 22.82 47.68 52.39
-99.997: 6938.62 332.29 426.50 80.26 451.07 75.49
-worst: 7593.98 728.06 820.22 303.62 838.66 54.02
-
-Windows 11 laptop, i7-1360P, Java 11
--Dthroughput=20000 -Durl=internal://
--------------------------------- SUMMARY (end to end) us -------------------------------------------
-Percentile run1 run2 run3 run4 run5 % Variation
-50.0: 1.60 1.70 1.60 1.60 1.60 3.84
-90.0: 2.10 2.10 2.10 2.10 2.00 3.16
-99.0: 23.39 22.62 22.18 20.70 17.12 17.65
-99.7: 155.90 168.19 191.74 177.92 170.24 8.54
-99.9: 857.09 723.97 824.32 816.13 764.93 8.46
-
--Dthroughput=20000
--------------------------------- SUMMARY (end to end) us -------------------------------------------
-Percentile run1 run2 run3 run4 run5 % Variation
-50.0: 24.93 24.67 24.93 24.80 24.93 0.69
-90.0: 37.95 35.26 38.34 35.52 35.14 5.72
-99.0: 1198.08 250.62 1243.14 469.50 477.70 72.53
- */
@SuppressWarnings("deprecation")
public class AccountManagerBenchmarkMain {
public static final int THROUGHPUT = Integer.getInteger("throughput", OS.isLinux() ? 100_000 : 10_000);
@@ -84,7 +53,7 @@ public static void main(String[] args) throws InterruptedException, MalformedURL
// This ExecutorService is used for running the client/gateway/service in the current process
ExecutorService es = Executors.newCachedThreadPool(new AffinityThreadFactory("test"));
- // Initialize a new instance of AccountManagerServiceMain. This is the main service for managing accounts.
+ // Initialise a new instance of AccountManagerServiceMain. This is the main service for managing accounts.
AccountManagerServiceMain service = null;
// Check if the host part of the URL is empty. If it is, that means we are running the service locally.
diff --git a/account/src/main/java/run/chronicle/account/AccountManagerClientMain.java b/account/src/main/java/run/chronicle/account/AccountManagerClientMain.java
index c55f18a..69cb476 100644
--- a/account/src/main/java/run/chronicle/account/AccountManagerClientMain.java
+++ b/account/src/main/java/run/chronicle/account/AccountManagerClientMain.java
@@ -14,6 +14,7 @@
import run.chronicle.account.util.LogsAccountManagerOut;
import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* This class acts as the main entry point for the AccountManagerClient.
* It creates a client which connects to a Chronicle server and performs various actions.
diff --git a/account/src/main/java/run/chronicle/account/benchmark-results.txt b/account/src/main/java/run/chronicle/account/benchmark-results.txt
new file mode 100644
index 0000000..45fb9d1
--- /dev/null
+++ b/account/src/main/java/run/chronicle/account/benchmark-results.txt
@@ -0,0 +1,29 @@
+-Xmx64m -Dthroughput=100000 -DrunTime=30 -Dbuffered=false -Durl=tcp://localhost:1248 -DaccountForCoordinatedOmission=false
+-------------------------------- SUMMARY (end to end) us -------------------------------------------
+Percentile run1 run2 run3 run4 run5 % Variation
+50.0: 10.99 10.99 11.02 11.02 10.99 0.19
+90.0: 18.02 17.89 15.70 11.22 11.12 28.86
+99.0: 20.96 19.94 15.86 15.86 15.79 14.89
+99.7: 34.11 21.02 16.11 15.92 15.89 17.73
+99.9: 42.18 22.62 16.67 16.34 16.18 21.00
+99.97: 2021.38 26.08 18.21 17.44 17.25 25.45
+99.99: 5480.45 60.48 44.48 22.82 47.68 52.39
+99.997: 6938.62 332.29 426.50 80.26 451.07 75.49
+worst: 7593.98 728.06 820.22 303.62 838.66 54.02
+
+Windows 11 laptop, i7-1360P, Java 11
+-Dthroughput=20000 -Durl=internal://
+-------------------------------- SUMMARY (end to end) us -------------------------------------------
+Percentile run1 run2 run3 run4 run5 % Variation
+50.0: 1.60 1.70 1.60 1.60 1.60 3.84
+90.0: 2.10 2.10 2.10 2.10 2.00 3.16
+99.0: 23.39 22.62 22.18 20.70 17.12 17.65
+99.7: 155.90 168.19 191.74 177.92 170.24 8.54
+99.9: 857.09 723.97 824.32 816.13 764.93 8.46
+
+-Dthroughput=20000
+-------------------------------- SUMMARY (end to end) us -------------------------------------------
+Percentile run1 run2 run3 run4 run5 % Variation
+50.0: 24.93 24.67 24.93 24.80 24.93 0.69
+90.0: 37.95 35.26 38.34 35.52 35.14 5.72
+99.0: 1198.08 250.62 1243.14 469.50 477.70 72.53
diff --git a/account/src/main/java/run/chronicle/account/domain/AccountService.java b/account/src/main/java/run/chronicle/account/domain/AccountService.java
new file mode 100644
index 0000000..5f42c0e
--- /dev/null
+++ b/account/src/main/java/run/chronicle/account/domain/AccountService.java
@@ -0,0 +1,94 @@
+package run.chronicle.account.domain;
+
+import net.openhft.chronicle.core.io.InvalidMarshallableException;
+import run.chronicle.account.dto.CreateAccount;
+import run.chronicle.account.dto.Transfer;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates all domain logic related to account creation, validation, and funds transfers.
+ * This service is responsible for:
+ * - Managing the in-memory store of accounts.
+ * - Validating currency, balances, and overdraft limits.
+ * - Updating account balances upon successful transfers.
+ */
+public class AccountService {
+
+ // In-memory account store
+ private final Map accountsMap = new LinkedHashMap<>();
+
+ /**
+ * Attempts to create a new account. Throws an InvalidMarshallableException if invalid,
+ * or returns a reason string if creation should fail due to domain rules.
+ */
+ public String tryCreateAccount(CreateAccount createAccount, long expectedTargetId) throws InvalidMarshallableException {
+ // Validate DTO properties
+ createAccount.validate();
+
+ if (createAccount.target() != expectedTargetId) {
+ return "target mismatch";
+ }
+
+ if (!(createAccount.balance() >= 0)) {
+ return "invalid balance";
+ }
+
+ Long accountNumber = createAccount.account();
+ if (accountsMap.containsKey(accountNumber)) {
+ return "account already exists";
+ }
+
+ // If valid, store the account
+ accountsMap.put(accountNumber, createAccount.deepCopy());
+ return null; // Indicates success
+ }
+
+ /**
+ * Attempts to perform a transfer. Returns a reason string if transfer fails domain checks,
+ * or null if the transfer is successful.
+ */
+ public String tryTransfer(Transfer transfer, long expectedTargetId) throws InvalidMarshallableException {
+ // Validate DTO properties
+ transfer.validate();
+
+ if (transfer.target() != expectedTargetId) {
+ return "target mismatch";
+ }
+
+ CreateAccount fromAccount = accountsMap.get(transfer.from());
+ if (fromAccount == null) {
+ return "from account doesn't exist";
+ }
+ if (fromAccount.currency() != transfer.currency()) {
+ return "from account currency doesn't match";
+ }
+
+ double amount = transfer.amount();
+ if (fromAccount.balance() + fromAccount.overdraft() < amount) {
+ return "insufficient funds";
+ }
+
+ CreateAccount toAccount = accountsMap.get(transfer.to());
+ if (toAccount == null) {
+ return "to account doesn't exist";
+ }
+ if (toAccount.currency() != transfer.currency()) {
+ return "to account currency doesn't match";
+ }
+
+ // Perform the transfer
+ fromAccount.balance(fromAccount.balance() - amount);
+ toAccount.balance(toAccount.balance() + amount);
+
+ return null; // Indicates success
+ }
+
+ /**
+ * Provides access to all accounts for checkpoint operations.
+ */
+ public Map getAllAccounts() {
+ return accountsMap;
+ }
+}
diff --git a/account/src/main/java/run/chronicle/account/dto/AbstractEvent.java b/account/src/main/java/run/chronicle/account/dto/AbstractEvent.java
index f9ed76f..8a819ba 100644
--- a/account/src/main/java/run/chronicle/account/dto/AbstractEvent.java
+++ b/account/src/main/java/run/chronicle/account/dto/AbstractEvent.java
@@ -30,20 +30,25 @@
* It provides common properties like sender, target, and sending time,
* along with fluent setter methods for easy chaining.
*
+ *
Concrete subclasses must ensure that a valid sender, target, and sendingTime
+ * are provided before use. Validation throws an {@link InvalidMarshallableException}
+ * if any required field is not set.
+ *
* @param the type of the event extending {@code AbstractEvent}
*/
@SuppressWarnings("unchecked")
public abstract class AbstractEvent>
extends SelfDescribingMarshallable
implements Validatable {
+
@ShortText
- private long sender; // sender represented in ShortText
+ private long sender;
@ShortText
- private long target; // target represented in ShortText
+ private long target;
@NanoTime
- private long sendingTime; // sendingTime represented as a unique timestamp in nanoseconds
+ private long sendingTime;
/**
* Retrieves the sender identifier.
@@ -86,7 +91,7 @@ public E target(long target) {
}
/**
- * Retrieves the sending time.
+ * Retrieves the sending wall clock time since epoch.
*
* @return the sending time in nanoseconds
*/
@@ -95,7 +100,7 @@ public long sendingTime() {
}
/**
- * Sets the sending time and returns the updated object.
+ * Sets the sending wall clock time since epoch and returns the updated object.
*
* @param sendingTime the sending time to set
* @return the updated object
@@ -106,17 +111,18 @@ public E sendingTime(long sendingTime) {
}
/**
- * The validate method is used to verify that all necessary properties have been set.
+ * Validates that all required fields (sender, target, and sendingTime) have been set.
+ * If any field is unset (0 indicates an unset value), it throws an {@link InvalidMarshallableException}.
*
* @throws InvalidMarshallableException If any of these properties is not set
*/
@Override
public void validate() throws InvalidMarshallableException {
if (sender == 0)
- throw new InvalidMarshallableException("sender must be set"); // ensure sender is set
+ throw new InvalidMarshallableException("sender must be set");
if (target == 0)
- throw new InvalidMarshallableException("target must be set"); // ensure target is set
+ throw new InvalidMarshallableException("target must be set");
if (sendingTime == 0)
- throw new InvalidMarshallableException("sendingTime must be set"); // ensure sendingTime is set
+ throw new InvalidMarshallableException("sendingTime must be set");
}
}
diff --git a/account/src/main/java/run/chronicle/account/dto/CheckPoint.java b/account/src/main/java/run/chronicle/account/dto/CheckPoint.java
index 0c547ba..f8b4309 100644
--- a/account/src/main/java/run/chronicle/account/dto/CheckPoint.java
+++ b/account/src/main/java/run/chronicle/account/dto/CheckPoint.java
@@ -19,8 +19,22 @@
/**
- * The {@code CheckPoint} class represents a request to dump all the current state of the system.
- * It extends {@link AbstractEvent} to include common event properties.
+ * The {@code CheckPoint} class represents a request to produce a snapshot
+ * (or "dump") of the entire current system state at a given moment in time.
+ * This includes all accounts and their balances, ensuring that the state
+ * can be recorded for audit, recovery, or analysis.
+ *
+ * A valid {@code CheckPoint} event must have all these fields set; validation
+ * is performed automatically when the event is processed.
+ *
+ *
*/
public class CheckPoint extends AbstractEvent {
+ // The CheckPoint event leverages the common fields and validation logic provided by AbstractEvent.
}
diff --git a/account/src/main/java/run/chronicle/account/dto/CreateAccount.java b/account/src/main/java/run/chronicle/account/dto/CreateAccount.java
index d21b604..3ba80f2 100644
--- a/account/src/main/java/run/chronicle/account/dto/CreateAccount.java
+++ b/account/src/main/java/run/chronicle/account/dto/CreateAccount.java
@@ -22,18 +22,36 @@
import net.openhft.chronicle.wire.converter.ShortText;
/**
- * Represents the event of creating a new account.
- * This class extends {@link AbstractEvent} and adds properties specific to account creation,
- * such as the account holder's name, account number, currency, balance, and overdraft limit.
- * Setters are designed using the fluent interface pattern for method chaining.
+ * Represents an event for creating a new account. This event includes all necessary details
+ * about the account to be created, such as:
+ *
+ *
name: The account holder's name.
+ *
account: A unique identifier for this account.
+ *
currency: The currency code for the account (stored as an integer code).
+ *
balance: The initial balance of the account. Must be ≥ 0.
+ *
overdraft: The overdraft limit for the account. Must be ≥ 0.
+ *
+ *
+ *
This class uses a fluent interface style for setter methods, allowing for chaining:
+ *
*/
public class CreateAccount extends AbstractEvent {
- private String name; // Name associated with the account
- private long account; // Account identifier
+ private String name;
+ private long account;
@ShortText
- private int currency; // Currency for the account
- private double balance; // Initial Balance of the account
- private double overdraft; // Overdraft limit of the account
+ private int currency;
+ private double balance;
+ private double overdraft;
/**
* Retrieves the account identifier.
@@ -76,7 +94,8 @@ public CreateAccount name(String name) {
}
/**
- * Retrieves the currency code.
+ * Returns the currency code of the account.
+ * This is typically an integer code mapping to a currency (e.g., EUR, USD).
*
* @return the currency code
*/
@@ -85,10 +104,11 @@ public int currency() {
}
/**
- * Sets the currency code.
+ * Sets the currency code and returns this instance.
+ * It is expected that the caller uses predefined integer codes for currencies.
*
* @param currency the currency code to set (e.g., "EUR", "USD")
- * @return this object for method chaining
+ * @return this instance for method chaining
*/
public CreateAccount currency(int currency) {
this.currency = currency;
@@ -105,7 +125,8 @@ public double balance() {
}
/**
- * Sets the initial balance of the account.
+ * Sets the initial balance of the account and returns this instance.
+ * The balance must be ≥ 0.
*
* @param balance the balance to set
* @return this object for method chaining
@@ -125,7 +146,8 @@ public double overdraft() {
}
/**
- * Sets the overdraft limit of the account.
+ * Sets the overdraft limit of the account and returns this instance.
+ * The overdraft limit must be ≥ 0.
*
* @param overdraft the overdraft limit to set
* @return this object for method chaining
@@ -136,7 +158,8 @@ public CreateAccount overdraft(double overdraft) {
}
/**
- * Validates that all necessary properties have been set and are valid.
+ * Validates that all required properties (sender, target, sendingTime, name, account, currency, balance, overdraft)
+ * have been set correctly.
*
* @throws InvalidMarshallableException if validation fails
*/
diff --git a/account/src/main/java/run/chronicle/account/dto/CreateAccountFailed.java b/account/src/main/java/run/chronicle/account/dto/CreateAccountFailed.java
index 155454f..6955036 100644
--- a/account/src/main/java/run/chronicle/account/dto/CreateAccountFailed.java
+++ b/account/src/main/java/run/chronicle/account/dto/CreateAccountFailed.java
@@ -21,15 +21,27 @@
import net.openhft.chronicle.core.io.InvalidMarshallableException;
/**
- * This class, CreateAccountFailed, is an extension of AbstractEvent used to represent a situation
- * where an attempt to create an account has failed. This class adds two properties to the event:
- * a reference to the original CreateAccount object that failed, and a reason string describing why
- * the account creation failed. As with other classes in this system, it uses a fluent style of
- * setters, and includes a validate method to ensure all necessary properties have been set.
+ * Represents an event indicating that an attempt to create an account has failed.
+ * This event includes:
+ *
+ *
createAccount: The original {@link CreateAccount} request that failed.
+ *
reason: A descriptive message explaining why the creation failed.
*/
public class CreateAccountFailed extends AbstractEvent {
- private CreateAccount createAccount; // Reference to the CreateAccount instance that failed
- private String reason; // The reason for the failure
+
+ private CreateAccount createAccount;
+ private String reason;
/**
* @return the CreateAccount instance that failed
@@ -71,6 +83,8 @@ public CreateAccountFailed reason(String reason) {
/**
* Validates that all necessary properties have been set and are valid.
+ * Ensures that the original {@link CreateAccount} request and the reason are present
+ * and valid. If validation fails, an {@link InvalidMarshallableException} is thrown.
*
* @throws InvalidMarshallableException if validation fails
*/
diff --git a/account/src/main/java/run/chronicle/account/dto/OnCreateAccount.java b/account/src/main/java/run/chronicle/account/dto/OnCreateAccount.java
index 867d63c..2cb683a 100644
--- a/account/src/main/java/run/chronicle/account/dto/OnCreateAccount.java
+++ b/account/src/main/java/run/chronicle/account/dto/OnCreateAccount.java
@@ -21,14 +21,21 @@
import net.openhft.chronicle.core.io.InvalidMarshallableException;
/**
- * The class OnCreateAccount is an extension of the AbstractEvent class,
- * and it represents an event that occurs when a CreateAccount action has successfully occurred.
- * The class contains a reference to the CreateAccount instance that initiated the event.
- * This class follows the convention of using a fluent style for its setters,
- * and it also includes a validate method to make sure that the createAccount field has been properly set.
+ * Represents an event indicating that an account was successfully created.
+ * This event references the original {@link CreateAccount} request that
+ * led to the successful creation.
+ *
+ *
*/
public class OnCreateAccount extends AbstractEvent {
- private CreateAccount createAccount; // The CreateAccount instance that triggered this event
+ private CreateAccount createAccount;
/**
* Retrieves the {@link CreateAccount} instance that triggered this event.
@@ -51,18 +58,22 @@ public OnCreateAccount createAccount(CreateAccount createAccount) {
}
/**
- * Validates that all necessary properties have been set and are valid.
+ * Validates that all required properties are set and valid. This includes:
+ *
+ *
All fields from the superclass (sender, target, sendingTime)
+ *
A non-null {@link CreateAccount} instance
+ *
Validation of the {@code CreateAccount} instance itself
+ *
*
- * @throws InvalidMarshallableException if validation fails
+ * @throws InvalidMarshallableException if validation fails for this event
*/
@Override
public void validate() throws InvalidMarshallableException {
super.validate(); // Validate fields in the superclass
if (createAccount == null) {
- throw new InvalidMarshallableException("CreateAccount must be set");
- } else {
- createAccount.validate(); // Validate the CreateAccount instance
+ throw new InvalidMarshallableException("Invalid OnCreateAccount: 'createAccount' must not be null.");
}
+ createAccount.validate(); // Validate the associated CreateAccount object
}
}
diff --git a/account/src/main/java/run/chronicle/account/dto/OnTransfer.java b/account/src/main/java/run/chronicle/account/dto/OnTransfer.java
index 5536111..d23a9c2 100644
--- a/account/src/main/java/run/chronicle/account/dto/OnTransfer.java
+++ b/account/src/main/java/run/chronicle/account/dto/OnTransfer.java
@@ -21,15 +21,21 @@
import net.openhft.chronicle.core.io.InvalidMarshallableException;
/**
- * Represents an event that occurs when a {@code Transfer} action takes place.
- * This class extends {@link AbstractEvent} and encapsulates a reference to
- * the {@link Transfer} instance that initiated this event.
- *
- * The class follows the Fluent Interface pattern for setter methods,
- * allowing for method chaining.
+ * Represents an event indicating that a funds transfer has taken place.
+ * This class extends {@link AbstractEvent}, adding a reference to the
+ * {@link Transfer} instance that initiated the event.
+ *
+ *
The class follows a fluent interface pattern for setter methods:
+ *
*/
public class OnTransfer extends AbstractEvent {
- private Transfer transfer; // The Transfer instance that triggered this event
+ private Transfer transfer;
/**
* Retrieves the {@link Transfer} instance that triggered this event.
@@ -52,7 +58,9 @@ public OnTransfer transfer(Transfer transfer) {
}
/**
- * The validate method is used to verify that all necessary properties have been set.
+ * Validates that all required properties have been set and are valid. This includes
+ * the fields inherited from {@link AbstractEvent} (sender, target, sendingTime) and
+ * the {@code Transfer} instance itself.
*
* @throws InvalidMarshallableException If any of these properties is not set
*/
@@ -61,17 +69,16 @@ public void validate() throws InvalidMarshallableException {
super.validate(); // Validate fields in the superclass
if (transfer == null) {
- throw new InvalidMarshallableException("Transfer must be set");
- } else {
- transfer.validate(); // Validate the Transfer instance
+ throw new InvalidMarshallableException("Invalid OnTransfer event: 'transfer' must not be null.");
}
+ transfer.validate();
}
/**
- * Overridden to specify the message format. In this case, it uses a lower level binary format,
- * not a self-describing message.
+ * Indicates that this event does not use a self-describing message format and instead
+ * relies on a more compact binary representation.
*
- * @return {@code false} as it does not use a self-describing message
+ * @return {@code false} as this event does not use a self-describing message format
*/
@Override
public boolean usesSelfDescribingMessage() {
diff --git a/account/src/main/java/run/chronicle/account/dto/Transfer.java b/account/src/main/java/run/chronicle/account/dto/Transfer.java
index abfd525..bf9f3e3 100644
--- a/account/src/main/java/run/chronicle/account/dto/Transfer.java
+++ b/account/src/main/java/run/chronicle/account/dto/Transfer.java
@@ -21,17 +21,29 @@
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.io.InvalidMarshallableException;
import net.openhft.chronicle.wire.converter.ShortText;
+
/**
- * The Transfer class extends AbstractEvent and represents a transfer event in the banking system.
- * It encapsulates all necessary details of a transfer, including the account numbers of both sender and receiver, the currency, the amount, and a reference to the transaction details. Like the previous classes,
- * it also provides a fluent interface for setters and includes a validate method to ensure all required fields are set.
+ * Represents a funds transfer operation between two accounts.
+ *
+ *
*/
public class Transfer extends AbstractEvent {
- private long from, to; // The account numbers for the transfer
+ private long from, to;
@ShortText
- private int currency; // The currency of the transfer, represented in ShortText format
- private double amount; // The amount to be transferred
- private Bytes> reference = Bytes.allocateElasticOnHeap(); // Reference to the transaction details
+ private int currency;
+ private double amount;
+ private final Bytes> reference = Bytes.allocateElasticOnHeap();
/**
* Retrieves the sender's account number.
@@ -83,7 +95,8 @@ public int currency() {
}
/**
- * Sets the currency code of the transfer.
+ * Sets the currency code of the transfer and returns this instance.
+ * The currency code should typically map to a known currency.
*
* @param currency the currency code to set (e.g., "EUR", "USD")
* @return this object for method chaining
@@ -114,7 +127,8 @@ public Transfer amount(double amount) {
}
/**
- * Retrieves the reference to the transaction details.
+ * Returns the reference data associated with this transfer.
+ * This could be a note, a reference number, or any additional context.
*
* @return the transaction reference
*/
@@ -123,7 +137,8 @@ public Bytes> reference() {
}
/**
- * Sets the reference to the transaction details.
+ * Sets the reference details for this transfer and returns this instance.
+ * The provided {@code Bytes} data is appended after clearing the existing reference.
*
* @param reference the reference to the transaction details
* @return the updated object
@@ -134,9 +149,17 @@ public Transfer reference(Bytes> reference) {
}
/**
- * The validate method is used to verify that all necessary properties have been set.
+ * Validates that all required fields have been set and are valid.
+ * This includes:
+ *
+ *
from: must be nonzero
+ *
to: must be nonzero
+ *
currency: must be nonzero
+ *
amount: must be positive
+ *
reference: must be non-null and non-empty
+ *
*
- * @throws InvalidMarshallableException If any of these properties is not set
+ * @throws InvalidMarshallableException if any validation check fails
*/
@Override
public void validate() throws InvalidMarshallableException {
@@ -154,10 +177,9 @@ public void validate() throws InvalidMarshallableException {
}
/**
- * Overridden to specify the message format. In this case, it uses a lower level binary format,
- * not a self-describing message.
+ * Specifies that this event uses a lower-level binary format rather than a self-describing message.
*
- * @return false as it does not use a self-describing message.
+ * @return {@code false}, indicating a non-self-describing message format
*/
@Override
public boolean usesSelfDescribingMessage() {
diff --git a/account/src/main/java/run/chronicle/account/dto/TransferFailed.java b/account/src/main/java/run/chronicle/account/dto/TransferFailed.java
index 57c46d2..39fec4b 100644
--- a/account/src/main/java/run/chronicle/account/dto/TransferFailed.java
+++ b/account/src/main/java/run/chronicle/account/dto/TransferFailed.java
@@ -23,11 +23,19 @@
/**
* The TransferFailed class is a type of AbstractEvent that represents a failed transfer operation in the system.
* It contains the Transfer object that failed and the reason for the failure.
- * Like other classes, it also follows the Fluent Interface pattern for setters, allowing chaining of method calls.
+ *
*/
public class TransferFailed extends AbstractEvent {
- private Transfer transfer; // The original transfer that failed
- private String reason; // The reason for the failure
+ private Transfer transfer;
+ private String reason;
/**
* Retrieves the original {@link Transfer} that failed.
@@ -70,21 +78,26 @@ public TransferFailed reason(String reason) {
}
/**
- * Validates that all necessary properties have been set and are valid.
+ * Validates that all required fields have been set and are valid. This includes:
+ *
+ *
All fields from {@link AbstractEvent} (sender, target, sendingTime)
+ *
A non-null {@link Transfer} instance, which is itself validated
+ *
A non-null reason string
+ *
*
- * @throws InvalidMarshallableException if validation fails
+ * @throws InvalidMarshallableException if any required field is missing or invalid
*/
@Override
public void validate() throws InvalidMarshallableException {
super.validate(); // Validate fields in the superclass
if (transfer == null) {
- throw new InvalidMarshallableException("Transfer must be set");
- } else {
- transfer.validate(); // Validate the Transfer instance
+ throw new InvalidMarshallableException("Invalid TransferFailed: 'transfer' must not be null.");
}
+ transfer.validate(); // Validate the Transfer object
- if (reason == null)
- throw new InvalidMarshallableException("reason must be set"); // Ensure 'reason' is set
+ if (reason == null) {
+ throw new InvalidMarshallableException("Invalid TransferFailed: 'reason' must not be null.");
+ }
}
}
diff --git a/account/src/main/java/run/chronicle/account/impl/AccountManagerImpl.java b/account/src/main/java/run/chronicle/account/impl/AccountManagerImpl.java
index 4bead7a..d233661 100644
--- a/account/src/main/java/run/chronicle/account/impl/AccountManagerImpl.java
+++ b/account/src/main/java/run/chronicle/account/impl/AccountManagerImpl.java
@@ -22,176 +22,77 @@
import net.openhft.chronicle.wire.SelfDescribingMarshallable;
import run.chronicle.account.api.AccountManagerIn;
import run.chronicle.account.api.AccountManagerOut;
+import run.chronicle.account.domain.AccountService;
import run.chronicle.account.dto.*;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
import static net.openhft.chronicle.core.time.SystemTimeProvider.CLOCK;
/**
- * AccountManagerImpl is the implementation of the AccountManagerIn interface.
- * It handles account creation, money transfers and checkpoints in the account management system.
+ * This class now primarily orchestrates the handling of events, delegating all
+ * domain logic (account validation, fund transfers, currency checks) to the
+ * AccountService.
*/
-public class AccountManagerImpl
- extends SelfDescribingMarshallable
- implements AccountManagerIn {
- private transient final AccountManagerOut out;
- // use a primitive long map
- private final Map accountsMap = new LinkedHashMap<>();
- // DTOs for events out
+public class AccountManagerImpl extends SelfDescribingMarshallable implements AccountManagerIn {
+ private final AccountManagerOut out;
+ private final AccountService accountService;
+
+ // Reusable event objects
private final OnCreateAccount onCreateAccount = new OnCreateAccount();
private final CreateAccountFailed createAccountFailed = new CreateAccountFailed();
private final OnTransfer onTransfer = new OnTransfer();
private final TransferFailed transferFailed = new TransferFailed();
+
private long id;
- /**
- * The constructor for the AccountManagerImpl class.
- *
- * @param out An instance of AccountManagerOut, which handles output events.
- */
public AccountManagerImpl(AccountManagerOut out) {
+ this(out, new AccountService());
+ }
+
+ public AccountManagerImpl(AccountManagerOut out, AccountService accountService) {
this.out = out;
+ this.accountService = accountService;
}
- /**
- * Sets the id of the AccountManagerImpl instance.
- *
- * @param id A long representing the id to be set.
- * @return This AccountManagerImpl instance.
- */
public AccountManagerImpl id(long id) {
this.id = id;
return this;
}
- /**
- * Handles account creation.
- *
- * @param createAccount An instance of CreateAccount containing details of the account to be created.
- * @throws InvalidMarshallableException If there's an error during the process.
- */
@Override
public void createAccount(CreateAccount createAccount) throws InvalidMarshallableException {
- // Verify if the account creation request is intended for this instance by checking the target of the request against the id of this instance
- // If they don't match, a failure message is sent with the reason "target mismatch" and the method returns
- if (createAccount.target() != id) {
- sendCreateAccountFailed(createAccount, "target mismatch");
- return;
- }
-
- // Verify if the initial balance for the account is greater than or equal to 0
- // If it isn't, a failure message is sent with the reason "invalid balance" and the method returns
- if (!(createAccount.balance() >= 0)) {
- sendCreateAccountFailed(createAccount, "invalid balance");
- return;
- }
-
- // Get the account number for the new account
- Long account = createAccount.account();
-
- // Check if the account already exists in the accounts map
- // If it does, a failure message is sent with the reason "account already exists" and the method returns
- if (accountsMap.containsKey(account)) {
- sendCreateAccountFailed(createAccount, "account already exists");
- return;
+ String failureReason = accountService.tryCreateAccount(createAccount, id);
+ if (failureReason == null) {
+ sendOnCreateAccount(createAccount);
+ } else {
+ sendCreateAccountFailed(createAccount, failureReason);
}
-
- // If all checks pass, create a copy of the CreateAccount object and add it to the accounts map
- // This is to ensure we retain a version of the data, even if the original CreateAccount object changes later
- accountsMap.put(account, createAccount.deepCopy());
-
- // Send a confirmation message indicating the account was successfully created
- sendOnCreateAccount(createAccount);
}
-
- /**
- * Handles transfers between accounts.
- *
- * @param transfer An instance of Transfer containing details of the transfer to be performed.
- */
@Override
public void transfer(Transfer transfer) {
- // Verify if the transfer is intended for this instance by checking the target of the transfer against the id of this instance
- // If it doesn't match, a failure message is sent with the reason "target mismatch" and the method returns
- if (transfer.target() != id) {
- sendTransferFailed(transfer, "target mismatch");
- return;
- }
-
- // Get the account from which funds are to be transferred
- // If it doesn't exist, a failure message is sent with the reason "from account doesn't exist" and the method returns
- CreateAccount fromAccount = accountsMap.get(transfer.from());
- if (fromAccount == null) {
- sendTransferFailed(transfer, "from account doesn't exist");
- return;
+ try {
+ String failureReason = accountService.tryTransfer(transfer, id);
+ if (failureReason == null) {
+ sendOnTransfer(transfer);
+ } else {
+ sendTransferFailed(transfer, failureReason);
+ }
+ } catch (InvalidMarshallableException e) {
+ sendTransferFailed(transfer, e.getMessage());
}
-
- // Check if the currency of the transfer matches the currency of the "from" account
- // If they don't match, a failure message is sent with the reason "from account currency doesn't match" and the method returns
- if (fromAccount.currency() != transfer.currency()) {
- sendTransferFailed(transfer, "from account currency doesn't match");
- return;
- }
-
- double amount = transfer.amount();
-
- // Check if the balance of the "from" account is sufficient to perform the transfer
- // If it isn't, a failure message is sent with the reason "insufficient funds" and the method returns
- if (fromAccount.balance() + fromAccount.overdraft() < amount) {
- sendTransferFailed(transfer, "insufficient funds");
- return;
- }
-
- // Get the account to which funds are to be transferred
- // If it doesn't exist, a failure message is sent with the reason "to account doesn't exist" and the method returns
- CreateAccount toAccount = accountsMap.get(transfer.to());
- if (toAccount == null) {
- sendTransferFailed(transfer, "to account doesn't exist");
- return;
- }
-
- // Check if the currency of the transfer matches the currency of the "to" account
- // If they don't match, a failure message is sent with the reason "to account currency doesn't match" and the method returns
- if (toAccount.currency() != transfer.currency()) {
- sendTransferFailed(transfer, "to account currency doesn't match");
- return;
- }
-
- // Perform the transfer: deduct the amount from the "from" account and add it to the "to" account
- fromAccount.balance(fromAccount.balance() - amount);
- toAccount.balance(toAccount.balance() + amount);
-
- // Send a confirmation message indicating the transfer was successful
- sendOnTransfer(transfer);
}
- /**
- * Handles checkpointing in the account management system.
- *
- * @param checkPoint An instance of CheckPoint which provides details of the checkpoint.
- */
@Override
public void checkPoint(CheckPoint checkPoint) {
- // Check if the checkpoint target matches the ID of this instance. If it does not match, ignore this checkpoint.
- if (checkPoint.target() != id)
- return; // ignored
+ if (checkPoint.target() != id) {
+ // Ignoring checkpoint as target does not match
+ return;
+ }
- // Start the checkpoint process. This is typically when we serialize and store the state of the system
- // to an output stream (out). CheckPoint instance contains details about the checkpoint.
out.startCheckpoint(checkPoint);
-
- // Iterate over all accounts in the accountsMap. For each account, send a "create account" event
- // This essentially stores the current state of each account to the output stream. This is useful in case
- // we need to restore the state of the system from this checkpoint in the future.
- for (CreateAccount createAccount : accountsMap.values()) {
- sendOnCreateAccount(createAccount);
+ for (CreateAccount ca : accountService.getAllAccounts().values()) {
+ sendOnCreateAccount(ca);
}
-
- // End the checkpoint process. This is typically when we complete the serialization of the system state
- // and finalize the checkpoint data in the output stream.
out.endCheckpoint(checkPoint);
}
diff --git a/account/src/main/java/run/chronicle/account/util/ErrorListener.java b/account/src/main/java/run/chronicle/account/util/ErrorListener.java
index 9672cd8..cbd3ae9 100644
--- a/account/src/main/java/run/chronicle/account/util/ErrorListener.java
+++ b/account/src/main/java/run/chronicle/account/util/ErrorListener.java
@@ -18,16 +18,40 @@
package run.chronicle.account.util;
/**
- * This interface provides a method to handle JVM errors.
- * Implementations of this interface will define how these errors are handled.
+ * A functional interface for handling critical JVM-level errors in the system.
+ * Implementations can define custom error-handling strategies such as logging,
+ * triggering alerts, or performing cleanup and shutdown procedures.
+ *
+ * Using a replicated queue, this can be done on another machine to avoid
+ * the impact of GC or IO the monitoring system might have.
+ *
+ *
Example usage:
+ *
+ * public class LoggingErrorListener implements ErrorListener {
+ * public void jvmError(String msg) {
+ * // Log the error to monitoring system, a file, or console
+ * logger.error(msg);
+ * }
+ * }
+ *
+ *
By providing different {@code ErrorListener} implementations, the system
+ * can adapt to various environments (production vs. development) or integrate
+ * with different error-handling frameworks.
*/
+@FunctionalInterface
public interface ErrorListener {
/**
- * This method handles JVM errors.
- * It takes a String message which provides details about the error.
+ * Handles a critical JVM-level error.
+ *
+ * Implementations may choose to:
+ *
+ *
Log the error message to files or monitoring systems.
+ *
Send alerts to administrators.
+ *
Trigger a controlled shutdown or cleanup process.
+ *
*
- * @param msg a String providing details about the JVM error.
+ * @param msg a human-readable message providing details about the encountered JVM error.
*/
void jvmError(String msg);
}
diff --git a/account/src/main/java/run/chronicle/account/util/LogsAccountManagerOut.java b/account/src/main/java/run/chronicle/account/util/LogsAccountManagerOut.java
index dda222d..726b204 100644
--- a/account/src/main/java/run/chronicle/account/util/LogsAccountManagerOut.java
+++ b/account/src/main/java/run/chronicle/account/util/LogsAccountManagerOut.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright 2016-2022 chronicle.software
+ *
+ * https://chronicle.software
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package run.chronicle.account.util;
import net.openhft.chronicle.core.Jvm;
@@ -5,39 +22,80 @@
import run.chronicle.account.dto.*;
/**
- * Mock interface implementation of AccountManagerOut that logs everything
+ * A mock implementation of {@link AccountManagerOut} that logs all received events.
+ *
+ * This class is useful for testing, demonstration, or diagnostic purposes. It prints
+ * events to the console (via {@link Jvm} logging) without performing any real actions.
+ * This allows developers to trace the flow of events through the system and verify
+ * that commands and results are being produced as expected.
*/
public class LogsAccountManagerOut implements AccountManagerOut {
+
+ /**
+ * Logs the start of a checkpoint operation.
+ *
+ * @param checkPoint The checkpoint command indicating that a state snapshot should begin.
+ */
@Override
public void startCheckpoint(CheckPoint checkPoint) {
Jvm.debug().on(getClass(), "startCheckpoint " + checkPoint);
}
+ /**
+ * Logs the end of a checkpoint operation.
+ *
+ * @param checkPoint The checkpoint command indicating that the state snapshot should conclude.
+ */
@Override
public void endCheckpoint(CheckPoint checkPoint) {
Jvm.debug().on(getClass(), "endCheckpoint " + checkPoint);
}
+ /**
+ * Logs a successful account creation event.
+ *
+ * @param onCreateAccount The event confirming that an account has been created.
+ */
@Override
public void onCreateAccount(OnCreateAccount onCreateAccount) {
Jvm.debug().on(getClass(), "onCreateAccount " + onCreateAccount);
}
+ /**
+ * Logs a failed account creation attempt.
+ *
+ * @param createAccountFailed The event detailing why an account creation failed.
+ */
@Override
public void createAccountFailed(CreateAccountFailed createAccountFailed) {
Jvm.warn().on(getClass(), "createAccountFailed " + createAccountFailed);
}
+ /**
+ * Logs a successful funds transfer event.
+ *
+ * @param onTransfer The event confirming that a transfer has completed successfully.
+ */
@Override
public void onTransfer(OnTransfer onTransfer) {
Jvm.debug().on(getClass(), "onTransfer " + onTransfer);
}
+ /**
+ * Logs a failed transfer attempt.
+ *
+ * @param transferFailed The event detailing why a funds transfer failed.
+ */
@Override
public void transferFailed(TransferFailed transferFailed) {
Jvm.warn().on(getClass(), "transferFailed " + transferFailed);
}
+ /**
+ * Logs a critical JVM-level error event.
+ *
+ * @param msg A descriptive message about the JVM error encountered.
+ */
@Override
public void jvmError(String msg) {
Jvm.error().on(getClass(), "jvmError " + msg);
diff --git a/account/src/test/java/run/chronicle/account/AccountsTest.java b/account/src/test/java/run/chronicle/account/AccountsTest.java
index d2d69ec..2c08ce3 100644
--- a/account/src/test/java/run/chronicle/account/AccountsTest.java
+++ b/account/src/test/java/run/chronicle/account/AccountsTest.java
@@ -36,19 +36,32 @@
import static org.junit.Assert.assertEquals;
/**
- * This class AccountsTest is a test class that uses
- * JUnit's Parameterized runner to run multiple tests with different parameters.
- * The test parameters are set up in the parameters method
- * and are used to create an instance of YamlTester for each test.
- * Each test runs through the runTester method which sets the system clock to a specific time,
- * runs the test, and checks the output.
- * After each test, the system clock is reset to its default state in the tearDown method.
+ * The {@code AccountsTest} class uses JUnit's Parameterized runner to execute a suite
+ * of YAML-driven tests for the Account Management Service (AMS). Each test scenario is
+ * defined by YAML files representing inputs and expected outputs. The tests verify that
+ * the {@link AccountManagerImpl} responds correctly to various commands, including edge cases
+ * introduced via "agitators" that manipulate or remove fields to test the system's resilience.
+ *
+ *
Key Features:
+ *
+ *
Parameterized Testing: Multiple scenarios defined in YAML are run using
+ * the same code, providing comprehensive coverage of both normal and abnormal conditions.
+ *
Agitators for Robustness: Deliberate modifications (e.g., missing fields,
+ * invalid amounts) are introduced to ensure that the system handles validation failures gracefully.
+ *
Time Management: The system clock is controlled via a {@link SetTimeProvider}
+ * to produce deterministic timestamps, making tests reproducible and consistent.
+ *
Integration with Jinjava: Templates containing placeholders (e.g., {{...}})
+ * can be rendered at runtime, enabling dynamic test inputs or scenario generation.
+ *
*/
-// This class is used to run tests for the Account system.
@SuppressWarnings("deprecation")
@RunWith(Parameterized.class)
public class AccountsTest {
- // Defines the paths to the tests to run.
+
+ /**
+ * Paths to the YAML test directories. Each directory contains sets of input/output YAML files
+ * and may represent different categories of tests (e.g., simple scenarios, mixed scenarios, generated tests).
+ */
static final String paths = "" +
"account/simple," +
"account/simple-gen," +
@@ -58,51 +71,95 @@ public class AccountsTest {
"account/gpt-gen," +
"account/gpt-jinja," +
"account/bard-gen," +
- "account/bard-jinja";
+ "account/bard-jinja," +
+ "account/o1-pro";
+
+ /**
+ * The identifier ("vault") used as the system ID (target) in tests. This matches the requirements
+ * that commands and responses must reference a known target identifier.
+ */
static final long VAULT = ShortText.INSTANCE.parse("vault");
- // The name of the test, and the tester that will run the test.
+ /**
+ * The test name and YamlTester instance for each parameterized test run.
+ *
+ *
{@code name} is the scenario name (often derived from the directory name).
+ *
{@code tester} is the utility that loads YAML input, runs the {@link AccountManagerImpl},
+ * and compares the actual output against the expected output specified in the YAML files.
+ *
+ */
final String name;
final net.openhft.chronicle.wire.utils.YamlTester tester;
- // Constructor that sets the name and tester.
+ /**
+ * Constructs a single test parameter instance with a given scenario name and YamlTester.
+ *
+ * @param name A descriptive name for the test scenario.
+ * @param tester The YamlTester that will execute and verify the test scenario.
+ */
public AccountsTest(String name, net.openhft.chronicle.wire.utils.YamlTester tester) {
this.name = name;
this.tester = tester;
}
- // Defines the parameters for the parameterized test runner.
+ /**
+ * Provides a list of test parameters for the Parameterized runner.
+ *
+ * Uses {@link net.openhft.chronicle.wire.utils.YamlTesterParametersBuilder} to:
+ *
+ *
Create an {@link AccountManagerImpl} instance for each scenario.
+ *
Introduce "agitators" that simulate invalid or missing data, ensuring robustness and proper validation handling.
+ *
Optionally render templates if the YAML files contain Jinjava placeholders.
+ *
+ *
+ * @return A list of arrays, each containing a scenario name and a YamlTester instance.
+ */
@Parameterized.Parameters(name = "{0}")
public static List