This project implements an asynchronous transaction processing system using a microservices architecture and Apache Kafka.
The system validates financial transactions through an anti-fraud service and updates their status asynchronously.
Two independent microservices communicate using Kafka events:
Client → Transaction Service → Kafka → Fraud Service → Kafka → Transaction Service → DB Update
| Service | Responsibility |
|---|---|
| transaction-service | Creates transactions and updates their status |
| fraud-service | Evaluates fraud rules and returns validation result |
- Client creates a transaction → stored as PENDING
- transaction-service publishes
transaction-created - fraud-service consumes and evaluates rule
- fraud-service publishes
transaction-validated - transaction-service consumes and updates →
APPROVEDorREJECTED
| Topic | Description |
|---|---|
| transaction-created | New transaction event |
| transaction-validated | Fraud validation result |
| *.DLT | Dead Letter Topic for invalid messages |
Transactions greater than configured value are rejected:
fraud.rules.max-amount = 1000
The transaction service ignores duplicated validation events.
@Version is used to prevent concurrent updates.
Invalid messages are redirected to a Dead Letter Topic (DLT) instead of stopping the consumer.
Kafka type headers are disabled to avoid coupling services to Java classpaths.
- Docker
- Docker Compose
docker compose up --buildServices:
- transaction-service → http://localhost:8080
- fraud-service → http://localhost:8081
- kafka → localhost:9092
- postgres → localhost:5433
POST /transactions
{
"accountExternalIdDebit": "11111111-1111-1111-1111-111111111111",
"accountExternalIdCredit": "22222222-2222-2222-2222-222222222222",
"transferTypeId": 1,
"value": 120
}Response (initial):
{
"transactionExternalId": "uuid",
"transactionStatus": { "name": "PENDING" }
}After async processing:
{
"transactionExternalId": "uuid",
"transactionStatus": { "name": "APPROVED" }
}If value > 1000:
{
"transactionStatus": { "name": "REJECTED" }
}GET /transactions/{transactionExternalId}
Send invalid message:
kafka-console-producer --bootstrap-server kafka:9092 --topic transaction-validated
{"transactionExternalId":"abc","status":123}Consume DLT:
kafka-console-consumer --bootstrap-server kafka:9092 --topic transaction-validated.DLT --from-beginning- Event-driven architecture for loose coupling
- At-least-once processing semantics
- Idempotent consumers to handle retries
- Dead Letter Topic for resilience
- Externalized fraud rules configuration
- Add auditing fields such as updatedAt to track status changes and provide full transaction lifecycle visibility.
- Outbox Pattern for transactional publishing
- Schema Registry (Avro/Protobuf)
- Retry topics strategy
- Metrics and tracing (OpenTelemetry)
Hans Ponte