This is a simple transaction service implemented in Java using Spring Boot. It allows you to store transactions (along with their parent-child relationships), retrieve transaction IDs by type, and calculate the total amount of a transaction including all its descendants.
It is an implementation of the Mendel Java Code Challenge, specified in Java_Code_Challenge.pdf.
docker build -t transactions-service .
docker run --rm -p 8080:8080 transactions-service
The service listens on port 8080.
Requires Java 17+ installed locally (the Maven wrapper handles the rest).
./mvnw spring-boot:run
docker build -f Dockerfile.test -t transactions-service-test .
docker run --rm transactions-service-test
./mvnw test
Creates or overwrites the transaction with id {id}.
Request body:
{ "amount": 5000, "type": "cars", "parent_id": 10 }| Field | Type | Required | Notes |
|---|---|---|---|
amount |
double | yes | |
type |
string | yes | non-blank |
parent_id |
long | no | must reference an existing transaction |
Response:
{ "status": "ok" }Returns the ids of every transaction stored with the given type.
GET /transactions/types/cars
=> [10]
Unexistant types return an empty array, not an error.
Returns the sum of the transaction's own amount plus the amount of every transaction transitively
linked to it via parent_id (i.e. its full descendant subtree — not its ancestors).
GET /transactions/sum/10
=> { "sum": 20000.0 }
| Situation | Status | Body |
|---|---|---|
GET /transactions/sum/{id} for unknown id |
404 | { "error": "Transaction not found: {id}" } |
PUT with a parent_id that doesn't exist |
400 | { "error": "Invalid parent transaction: {id}" } |
PUT that would create a parent cycle |
400 | { "error": "Transaction parent cycle detected" } |
PUT /transactions/10 { "amount": 5000, "type": "cars" } => { "status": "ok" }
PUT /transactions/11 { "amount": 10000, "type": "shopping", "parent_id": 10 } => { "status": "ok" }
PUT /transactions/12 { "amount": 5000, "type": "shopping", "parent_id": 11 } => { "status": "ok" }
GET /transactions/types/cars => [10]
GET /transactions/sum/10 => { "sum": 20000.0 }
GET /transactions/sum/11 => { "sum": 15000.0 }
This exact sequence is covered by an integration test
(TransactionControllerIntegrationTest.reproducesTheChallengeSpecExample).
- Layers: for clarity and SOLID principles, the code is organized into three layers. Each layer only depends on interfaces from the layer below it. Specific implementations are later injected by Spring.
web(controllers, DTOs, error mapping)service(validation, business rules)repository(storage)
- Storage:
InMemoryTransactionRepositorykeeps the transactions in aMap<Long, Transaction>plus two secondary indexes to keep both GET endpoints O(1)/O(children) instead of scanning every transaction. - Parent validation and cycle detection are not specified in the spec but were added to show some edge case, exception handling.
- Thread-safety: This requirement is also not mentioned so a simple read-write lock is used to protect the app from concurrent access issues.