Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Candidate Solution

The implementation can be found in the `/solution` directory.

# Yape Code Challenge :rocket:

Our code challenge will let you marvel us with your Jedi coding skills :smile:.
Expand Down Expand Up @@ -80,3 +84,4 @@ You can use Graphql;
When you finish your challenge, after forking a repository, you **must** open a pull request to our repository. There are no limitations to the implementation, you can follow the programming paradigm, modularization, and style that you feel is the most appropriate solution.

If you have any questions, please let us know.

186 changes: 186 additions & 0 deletions solution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Yape Code Challenge — Event-Driven Transaction System

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.

---

## 🧱 Architecture

Two independent microservices communicate using Kafka events:

```
Client → Transaction Service → Kafka → Fraud Service → Kafka → Transaction Service → DB Update
```

### Services

| Service | Responsibility |
| ------------------- | --------------------------------------------------- |
| transaction-service | Creates transactions and updates their status |
| fraud-service | Evaluates fraud rules and returns validation result |

---

## 🔄 Transaction Flow

1. Client creates a transaction → stored as **PENDING**
2. transaction-service publishes `transaction-created`
3. fraud-service consumes and evaluates rule
4. fraud-service publishes `transaction-validated`
5. transaction-service consumes and updates → `APPROVED` or `REJECTED`

---

## 📡 Kafka Topics

| Topic | Description |
| --------------------- | -------------------------------------- |
| transaction-created | New transaction event |
| transaction-validated | Fraud validation result |
| *.DLT | Dead Letter Topic for invalid messages |

---

## ⚙️ Fraud Rule

Transactions greater than configured value are rejected:

```
fraud.rules.max-amount = 1000
```

---

## 🛡️ Reliability Features

### Idempotent Consumer

The transaction service ignores duplicated validation events.

### Optimistic Locking

`@Version` is used to prevent concurrent updates.

### Poison Message Handling

Invalid messages are redirected to a Dead Letter Topic (DLT) instead of stopping the consumer.

### Decoupled Serialization

Kafka type headers are disabled to avoid coupling services to Java classpaths.

---

## 🚀 Running the Project

### Requirements

* Docker
* Docker Compose

### Start system

```bash
docker compose up --build
```

Services:

* transaction-service → http://localhost:8080
* fraud-service → http://localhost:8081
* kafka → localhost:9092
* postgres → localhost:5433

---

## 🧪 API Usage

### Create Transaction

POST `/transactions`

```json
{
"accountExternalIdDebit": "11111111-1111-1111-1111-111111111111",
"accountExternalIdCredit": "22222222-2222-2222-2222-222222222222",
"transferTypeId": 1,
"value": 120
}
```

Response (initial):

```json
{
"transactionExternalId": "uuid",
"transactionStatus": { "name": "PENDING" }
}
```

After async processing:

```json
{
"transactionExternalId": "uuid",
"transactionStatus": { "name": "APPROVED" }
}
```

If value > 1000:

```json
{
"transactionStatus": { "name": "REJECTED" }
}
```

---

### Get Transaction

GET `/transactions/{transactionExternalId}`

---

## 🔍 Testing DLT

Send invalid message:

```bash
kafka-console-producer --bootstrap-server kafka:9092 --topic transaction-validated
{"transactionExternalId":"abc","status":123}
```

Consume DLT:

```bash
kafka-console-consumer --bootstrap-server kafka:9092 --topic transaction-validated.DLT --from-beginning
```

---

## 🧠 Design Decisions

* 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

---

## 📌 Possible Improvements

* 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)

---

## Author

Hans Ponte
47 changes: 47 additions & 0 deletions solution/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
services:

postgres:
image: postgres:15
container_name: postgres
environment:
POSTGRES_DB: transaction_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5433:5432"

zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181

kafka:
image: confluentinc/cp-kafka:7.5.0
container_name: kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

transaction-service:
build: ./transaction-service
container_name: transaction-service
depends_on:
- postgres
- kafka
ports:
- "8080:8080"

fraud-service:
build: ./fraud-service
container_name: fraud-service
depends_on:
- kafka
ports:
- "8081:8081"
2 changes: 2 additions & 0 deletions solution/fraud-service/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf
33 changes: 33 additions & 0 deletions solution/fraud-service/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
3 changes: 3 additions & 0 deletions solution/fraud-service/.mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
19 changes: 19 additions & 0 deletions solution/fraud-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ---------- BUILD STAGE ----------
FROM maven:3.9.9-eclipse-temurin-17 AS build
WORKDIR /workspace

COPY pom.xml .
COPY src ./src

RUN mvn -q -DskipTests clean package

# ---------- RUN STAGE ----------
FROM eclipse-temurin:17-jdk-alpine

WORKDIR /app

COPY --from=build /workspace/target/*.jar app.jar

EXPOSE 8081

ENTRYPOINT ["java","-jar","app.jar"]
Loading