A Spring Boot application for Bloomberg to analyze FX deals. The system accepts FX deal details, validates them, prevents duplicate imports, and persists data to PostgreSQL.
- ✅ REST API for FX deal management
- ✅ Validation of deal structure and data types
- ✅ Duplicate deal prevention
- ✅ No rollback policy - partial success allowed
- ✅ Comprehensive error handling and logging
- ✅ Unit tests with coverage reporting
- ✅ Docker Compose deployment
- ✅ PostgreSQL database
- ✅ Makefile for easy commands
- Java 17
- Spring Boot 3.2.0
- PostgreSQL 15
- Maven
- JUnit 5 & Mockito
- Docker & Docker Compose
- Lombok
- Java 17+
- Maven 3.6+
- Docker & Docker Compose
- Make (optional)
# Start application with Docker
make docker-up
# View logs
make docker-logs
# Stop application
make docker-down
# Run tests
make test
# Build project
make build# Build the project
./mvnw clean package -DskipTests
# Start with Docker Compose
docker-compose up --build -d
# View logs
docker-compose logs -f app
# Stop services
docker-compose downhttp://localhost:8080/api/fx-deals
POST /api/fx-deals
Request Body:
{
"dealUniqueId": "DEAL-2024-001",
"fromCurrencyIsoCode": "USD",
"toCurrencyIsoCode": "EUR",
"dealTimestamp": "2024-01-15T10:30:00",
"dealAmount": 10000.50
}Response (201 Created):
{
"id": 1,
"dealUniqueId": "DEAL-2024-001",
"fromCurrencyIsoCode": "USD",
"toCurrencyIsoCode": "EUR",
"dealTimestamp": "2024-01-15T10:30:00",
"dealAmount": 10000.50,
"createdAt": "2024-01-15T10:35:00"
}POST /api/fx-deals/import
Request Body (Array):
[
{
"dealUniqueId": "DEAL-2024-001",
"fromCurrencyIsoCode": "USD",
"toCurrencyIsoCode": "EUR",
"dealTimestamp": "2024-01-15T10:30:00",
"dealAmount": 10000.50
},
{
"dealUniqueId": "DEAL-2024-002",
"fromCurrencyIsoCode": "GBP",
"toCurrencyIsoCode": "JPY",
"dealTimestamp": "2024-01-15T11:45:00",
"dealAmount": 25000.75
}
]Response (201 Created or 206 Partial Content):
{
"totalRecords": 2,
"successCount": 2,
"failureCount": 0,
"errors": []
}GET /api/fx-deals
Response (200 OK):
[
{
"id": 1,
"dealUniqueId": "DEAL-2024-001",
"fromCurrencyIsoCode": "USD",
"toCurrencyIsoCode": "EUR",
"dealTimestamp": "2024-01-15T10:30:00",
"dealAmount": 10000.50,
"createdAt": "2024-01-15T10:35:00"
}
]# Import sample deals
curl -X POST http://localhost:8080/api/fx-deals/import \
-H "Content-Type: application/json" \
-d @sample-deals.json
# Get all deals
curl http://localhost:8080/api/fx-dealscurl -X POST http://localhost:8080/api/fx-deals \
-H "Content-Type: application/json" \
-d '{
"dealUniqueId": "TEST-001",
"fromCurrencyIsoCode": "USD",
"toCurrencyIsoCode": "EUR",
"dealTimestamp": "2024-01-15T10:30:00",
"dealAmount": 5000.00
}'# Try importing the same deal twice
curl -X POST http://localhost:8080/api/fx-deals \
-H "Content-Type: application/json" \
-d '{
"dealUniqueId": "TEST-001",
"fromCurrencyIsoCode": "USD",
"toCurrencyIsoCode": "EUR",
"dealTimestamp": "2024-01-15T10:30:00",
"dealAmount": 5000.00
}'
# Expected: 409 Conflict errordealUniqueId: Must not be blankfromCurrencyIsoCode: Must be 3-character ISO code (A-Z)toCurrencyIsoCode: Must be 3-character ISO code (A-Z)dealTimestamp: Must be valid LocalDateTimedealAmount: Must be greater than 0
- Deal unique ID must be unique across all deals
- Currency codes must be exactly 3 uppercase letters
- Deal amount must be positive
- Duplicate deals are rejected with 409 Conflict
When importing multiple deals:
- Each deal is processed independently
- Valid deals are saved even if others fail
- Response includes success/failure counts and error details
# Run all tests
./mvnw test
# Run with coverage
./mvnw clean test
# View coverage report
open target/site/jacoco/index.html- Service layer: 90%+
- Controller layer: 85%+
- Overall: 80%+
fx-deals-warehouse/
├── src/
│ ├── main/
│ │ ├── java/com/progresssoft/fxdeals/
│ │ │ ├── controller/ # REST controllers
│ │ │ ├── service/ # Business logic
│ │ │ ├── repository/ # Data access
│ │ │ ├── model/ # JPA entities
│ │ │ ├── dto/ # Data transfer objects
│ │ │ ├── exception/ # Custom exceptions & handlers
│ │ │ └── FxDealsWarehouseApplication.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ ├── java/com/progresssoft/fxdeals/
│ │ ├── controller/ # Controller tests
│ │ └── service/ # Service tests
│ └── resources/
│ └── application.properties
├── docker-compose.yml
├── Dockerfile
├── Makefile
├── pom.xml
├── sample-deals.json
└── README.md
CREATE TABLE fx_deals (
id BIGSERIAL PRIMARY KEY,
deal_unique_id VARCHAR(100) UNIQUE NOT NULL,
from_currency_iso_code VARCHAR(3) NOT NULL,
to_currency_iso_code VARCHAR(3) NOT NULL,
deal_timestamp TIMESTAMP NOT NULL,
deal_amount DECIMAL(19,4) NOT NULL,
created_at TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX idx_deal_id ON fx_deals(deal_unique_id);{
"timestamp": "2024-01-15T10:30:00",
"status": 400,
"errors": {
"dealUniqueId": "Deal Unique ID is required",
"fromCurrencyIsoCode": "From Currency must be 3 characters ISO code"
}
}{
"status": 409,
"message": "Deal with ID DEAL-2024-001 already exists",
"timestamp": "2024-01-15T10:30:00"
}{
"status": 500,
"message": "An unexpected error occurred",
"timestamp": "2024-01-15T10:30:00"
}Application logs include:
- Request processing
- Validation errors
- Duplicate deal attempts
- Database operations
- Exception stack traces
View logs:
# Docker logs
make docker-logs
# Or
docker-compose logs -f app- Start PostgreSQL:
docker run -d \
--name postgres \
-e POSTGRES_DB=fxdeals \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
postgres:15-alpine- Run application:
./mvnw spring-boot:run./mvnw clean package
java -jar target/fx-deals-warehouse-1.0.0.jar# Check what's using port 8080
lsof -i :8080
# Kill process
kill -9 <PID># Check if PostgreSQL is running
docker ps | grep postgres
# Restart database
docker-compose restart postgresmake clean
# Or
docker-compose down -v
./mvnw clean- Database index on
deal_unique_idfor fast duplicate checks - Batch import uses independent transactions (REQUIRES_NEW)
- Connection pooling via HikariCP
- Lazy loading for JPA entities
For production deployment:
- Change default database credentials
- Enable HTTPS/TLS
- Add authentication/authorization
- Implement rate limiting
- Use secrets management
- Enable CORS only for trusted origins
This project is developed for ProgressSoft Corporation assessment.
For questions or issues, contact Human Capital Department at ProgressSoft Corporation.