A complete product transaction system built with Spring Boot, implementing user and merchant modules for product purchases with prepaid cash accounts.
- User Module: User management, prepaid account management, account recharge via mocked payment gateway
- Merchant Module: Merchant management, product catalog, inventory management with audit trails
- Transaction Module: Order processing with atomic transactions (deducts user balance, credits merchant, deducts inventory)
- Reconciliation Module: Daily scheduled job to reconcile merchant account balance with calculated sales value
- Payment Gateway: Mocked REST API for account recharge (extensible for real banking APIs)
- Audit Trails: Complete transaction history for account and inventory changes
- Optimistic Locking: Concurrency control for balance and inventory updates
- RESTful API: Comprehensive REST API with Swagger/OpenAPI documentation
- Java: 17
- Spring Boot: 3.2.0
- Spring Cloud: 2023.0.0
- PostgreSQL: 15 (via Docker)
- Flyway: Database migration
- Lombok: Boilerplate reduction
- Swagger/OpenAPI: API documentation (SpringDoc OpenAPI 3)
- Spring Retry: Retry logic for optimistic locking failures
- JUnit 5 & Mockito: Unit testing
- Testcontainers: Integration testing with real PostgreSQL
- JaCoCo: Code coverage reporting
The system follows Domain-Driven Design (DDD) principles with clear boundary contexts:
- User Context: User and Account aggregates
- Merchant Context: Merchant, Product, and Inventory aggregates
- Transaction Context: Order and Payment aggregates
- Reconciliation Context: Reconciliation reports
- External Integration Context: Payment gateway adapters
src/main/java/com/mamoru/transactionsystem/
├── common/ # Common utilities
│ ├── config/ # Configuration classes
│ ├── dto/ # Shared DTOs
│ └── exception/ # Exception handling
├── user/ # User module
│ ├── domain/ # Domain entities
│ ├── application/ # Application services
│ ├── infrastructure/ # Repositories
│ └── presentation/ # REST controllers
├── merchant/ # Merchant module
├── transaction/ # Transaction module
├── reconciliation/ # Reconciliation module
└── payment-gateway/ # Payment gateway module
- Java 17 or higher
- Maven 3.6+
- Docker and Docker Compose
- IDE (IntelliJ IDEA, Eclipse, or VS Code)
cd /home/dev/Dev/Assessments/Mamorudocker-compose up -dThis will start PostgreSQL on port 5432 with:
- Database:
transaction_system - Username:
postgres - Password:
postgres
mvn clean installmvn spring-boot:runOr from your IDE, run the TransactionSystemApplication class.
The application will start on http://localhost:8080
Once the application is running, access Swagger UI at:
- Swagger UI: http://localhost:8080/swagger-ui.html
- API Docs: http://localhost:8080/api-docs
All endpoints are documented in Swagger UI. Access at: http://localhost:8080/swagger-ui.html
Base Path: /api/v1/users
POST /api/v1/users- Create a new user (automatically creates prepaid account)GET /api/v1/users/{userId}- Get user details by IDPOST /api/v1/users/{userId}/accounts/recharge- Recharge user prepaid account via mocked payment gatewayGET /api/v1/users/{userId}/accounts/balance- Get current account balance
Base Path: /api/v1/merchants
POST /api/v1/merchants- Create a new merchantGET /api/v1/merchants/{merchantId}- Get merchant details (including account balance)
Products (Base Path: /api/v1/merchants/{merchantId}/products)
POST /api/v1/merchants/{merchantId}/products- Create a new productGET /api/v1/merchants/{merchantId}/products- Get all products for a merchantGET /api/v1/merchants/{merchantId}/products/{productId}- Get product details by ID
Inventory (Base Path: /api/v1/merchants/{merchantId}/inventory)
POST /api/v1/merchants/{merchantId}/inventory/products/{productId}/add- Add quantity to inventoryGET /api/v1/merchants/{merchantId}/inventory- Get all inventory records for a merchantGET /api/v1/merchants/{merchantId}/inventory/products/{productId}- Get inventory for a specific product
Base Path: /api/v1/orders
POST /api/v1/orders- Place an order (requiresX-User-Idheader)- Atomically: deducts user balance, credits merchant, deducts inventory
GET /api/v1/orders/{orderId}- Get order details by order IDGET /api/v1/orders/order-number/{orderNumber}- Get order details by order number
Base Path: /api/v1/reconciliation/merchants/{merchantId}
POST /api/v1/reconciliation/merchants/{merchantId}/run- Manually trigger reconciliation for yesterdayPOST /api/v1/reconciliation/merchants/{merchantId}/run/{reportDate}- Manually trigger reconciliation for specific date (format: yyyy-MM-dd)GET /api/v1/reconciliation/merchants/{merchantId}/reports- Get all reconciliation reports for a merchantGET /api/v1/reconciliation/merchants/{merchantId}/reports/{reportDate}- Get reconciliation report for specific date (format: yyyy-MM-dd)
The application uses a .env file for environment-based configuration. This allows you to configure the application without modifying code files.
-
Copy the example file:
cp .env.example .env
-
Edit
.envfile with your configuration values:# Database Configuration DB_HOST=localhost DB_PORT=5432 DB_NAME=transaction_system DB_USERNAME=postgres DB_PASSWORD=postgres # Application Configuration SERVER_PORT=8080 APP_NAME=transaction-system # ... and more
-
The
.envfile is git-ignored - your local configuration won't be committed to the repository.
DB_HOST- Database host (default:localhost)DB_PORT- Database port (default:5432)DB_NAME- Database name (default:transaction_system)DB_USERNAME- Database username (default:postgres)DB_PASSWORD- Database password (default:postgres)
DB_CONTAINER_NAME- Docker container name for PostgreSQL (default:transaction-system-db)
SERVER_PORT- Server port (default:8080)APP_NAME- Application name (default:transaction-system)
RECONCILIATION_ENABLED- Enable/disable reconciliation (default:true)RECONCILIATION_CRON- Cron schedule (default:0 0 2 * * *- daily at 2 AM)DEFAULT_CURRENCY- Default currency (default:USD)
LOG_LEVEL_ROOT- Root log level (default:INFO)LOG_LEVEL_APP- Application log level (default:DEBUG)LOG_SQL_ENABLED- Enable SQL logging (default:false)
JPA_DDL_AUTO- DDL mode (default:validate)JPA_SHOW_SQL- Show SQL queries (default:false)JPA_BATCH_SIZE- Batch size (default:20)
HIKARI_MAX_POOL_SIZE- Maximum pool size (default:10)HIKARI_MIN_IDLE- Minimum idle connections (default:5)HIKARI_CONNECTION_TIMEOUT- Connection timeout in ms (default:30000)
FLYWAY_ENABLED- Enable Flyway migrations (default:true)
SWAGGER_ENABLED- Enable Swagger UI (default:true)SWAGGER_UI_PATH- Swagger UI path (default:/swagger-ui.html)
Configuration is loaded in the following order (highest priority first):
- System environment variables
.envfile (project root)application.ymldefaults
The docker-compose.yml file automatically loads variables from .env, so your database configuration will be consistent across the application and Docker containers.
The project includes comprehensive unit and integration tests:
Unit Tests:
- Domain entity tests (Account, Merchant, Inventory, Product, Order)
- Service layer tests (UserService, OrderService, InventoryService)
- Business logic validation and edge cases
Integration Tests:
- Repository integration tests (using Testcontainers with real PostgreSQL)
- Controller integration tests (full end-to-end API testing with MockMvc)
mvn testThis runs all unit tests by default. Integration tests are excluded by default (require Docker).
Integration tests require Docker to be running and accessible. To run all tests including integration tests:
mvn test -Dsurefire.excludedGroups=Note: Make sure Docker is running and your user has permission to access Docker. If you get permission errors, see Docker Permission Issues in the SETUP_GUIDE.md.
mvn test jacoco:reportCoverage report will be generated at: target/site/jacoco/index.html
The project targets 80% unit test coverage for core business logic:
- Domain entities: ✅ Covered
- Application services: ✅ Covered
- Controllers: ✅ Integration tests included
- Repositories: ✅ Integration tests included
- Unit Tests: Run by default with
mvn test - Integration Tests: Excluded by default, require Docker. Run with
mvn test -Dsurefire.excludedGroups= - Testcontainers: Real PostgreSQL container for integration tests
- Test Profile: Uses
application-test.ymlwith test-specific configuration - Flyway: Disabled in tests (uses
ddl-auto: create-dropinstead)
Database schema is managed by Flyway. Migrations are located in:
src/main/resources/db/migration/
The initial schema (V1__initial_schema.sql) creates all necessary tables:
- Users and Accounts
- Merchants, Products, and Inventory
- Orders and Payments
- Reconciliation Reports
Flyway runs automatically when the application starts, but you can also run migrations manually using Maven commands:
# Run all pending migrations
mvn flyway:migrate
# Run with custom database settings (if not using .env)
mvn flyway:migrate -DDB_HOST=localhost -DDB_PORT=5432 -DDB_NAME=transaction_system -DDB_USERNAME=postgres -DDB_PASSWORD=postgres# Check which migrations have been applied
mvn flyway:info# Validate that migrations haven't been modified
mvn flyway:validate# Repair the Flyway schema history table if it's corrupted
mvn flyway:repair# Mark an existing database as baseline (for databases that existed before Flyway)
mvn flyway:baseline# Drops all database objects (WARNING: This will delete all data!)
mvn flyway:clean-
Create a new migration file in
src/main/resources/db/migration/:- Naming convention:
V{version}__{description}.sql - Example:
V2__add_user_profile_table.sql - Example:
V3__add_indexes.sql
- Naming convention:
-
Migration files are versioned - Flyway tracks which migrations have been applied
-
Example migration file:
-- V2__add_user_profile_table.sql CREATE TABLE user_profiles ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, first_name VARCHAR(100), last_name VARCHAR(100), phone VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL );
- ✅ Never modify existing migration files after they've been applied to production
- ✅ Use version numbers sequentially (V1, V2, V3, ...)
- ✅ Test migrations on a copy of production data before applying
- ✅ Use descriptive names for migration files
- ✅ Keep migrations small and focused - one logical change per migration
- ✅ Always backup the database before running migrations in production
Migration commands respect your .env file configuration:
DB_HOST- Database hostDB_PORT- Database portDB_NAME- Database nameDB_USERNAME- Database usernameDB_PASSWORD- Database password
# Check if migration files exist in source
ls -la src/main/resources/db/migration/
# Check if migration files are in target (after compile)
ls -la target/classes/db/migration/
# If files are missing from target, rebuild:
mvn clean compile# Check Flyway migration history
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "SELECT version, description, installed_on, success FROM flyway_schema_history ORDER BY installed_rank;"
# Check if tables exist
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "\dt"
# Check column types (verify UUID conversion)
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "SELECT table_name, column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public' AND column_name = 'id' ORDER BY table_name;"If Flyway shows "No migrations found":
# 1. Clean and rebuild to copy resources
mvn clean compile
# 2. Verify files are in target
ls target/classes/db/migration/
# 3. Run migration
mvn flyway:migrateIf you deleted a migration file but it's still in the database history:
# Check current migration history
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "SELECT version, description FROM flyway_schema_history;"
# Remove a specific migration from history (replace '3' with the version to remove)
# WARNING: Only do this if the migration file no longer exists
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "DELETE FROM flyway_schema_history WHERE version = '3';"# Check users table structure
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "\d users"
# Check if any IDs are NULL
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "SELECT COUNT(*) as total_users, COUNT(id) as users_with_id, COUNT(*) FILTER (WHERE id IS NULL) as null_ids FROM users;"
# View sample data
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "SELECT id, username FROM users LIMIT 5;"If you need to completely reset the database:
# Option 1: Use Flyway clean (drops all objects)
mvn flyway:clean
mvn flyway:migrate
# Option 2: Drop and recreate database
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -c "DROP DATABASE IF EXISTS transaction_system;"
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -c "CREATE DATABASE transaction_system;"
mvn flyway:migrateIf you see index-related errors:
# List all indexes
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "\di"
# Check specific index
echo "password" | sudo -S docker exec transaction-system-db psql -U postgres -d transaction_system -c "\d+ orders"Issue: "No migrations found"
- Solution: Run
mvn clean compilefirst, thenmvn flyway:migrate
Issue: "Migration V3 already applied but file missing"
- Solution: Remove from history:
DELETE FROM flyway_schema_history WHERE version = '3';
Issue: "Index already exists" warnings
- Solution: This is normal if using
CREATE INDEX IF NOT EXISTS. The warnings are harmless.
Issue: "Column type mismatch"
- Solution: Verify migration V2 was applied. Check with:
SELECT data_type FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'id';
The project includes a seeding script to populate the database with sample data for development and testing.
-
5 Sample Users with accounts and initial balances:
john_doe- $1000.00jane_smith- $500.00bob_wilson- $750.00alice_brown- $2000.00charlie_davis- $100.00
-
4 Sample Merchants:
- Tech Store
- Fashion Boutique
- Electronics Hub
- Book Emporium
-
Multiple Products across all merchants with inventory
-
Sample Account Transactions (recharges)
./scripts/seed-database.shThe script will:
- Check database connection
- Ask for confirmation
- Run the seeding SQL script
- Display a summary
# Run the shell script via Maven
mvn exec:exec@seed-database
# Or run SQL directly via Maven
mvn sql:execute@seed-database-sql# Load environment variables from .env
export $(grep -v '^#' .env | xargs)
# Run the seed script
psql -h ${DB_HOST:-localhost} -p ${DB_PORT:-5432} -U ${DB_USERNAME:-postgres} -d ${DB_NAME:-transaction_system} -f src/main/resources/db/seeds/seed_data.sqlThe seed script is located at:
src/main/resources/db/seeds/seed_data.sql
- The seed script uses
ON CONFLICT DO NOTHING/ON CONFLICT DO UPDATEto be idempotent - You can run it multiple times safely - it will update existing data or skip duplicates
- To reset seed data, you can clean the database first:
mvn flyway:clean(⚠️ WARNING: deletes all data)
Edit src/main/resources/db/seeds/seed_data.sql to customize:
- Number of users
- Initial account balances
- Merchants and their products
- Inventory quantities
- Sample transactions
- Create Feature Branch:
git checkout -b feature/your-feature-name - Implement Feature: Follow DDD principles and maintain test coverage
- Run Tests: Ensure all tests pass and coverage is maintained
- Commit Changes:
git commit -m "feat: your feature description" - Push and Create PR: Push to remote and create pull request
- Atomic Operations: Order processing ensures all-or-nothing execution
- Optimistic Locking: Version-based concurrency control for balance and inventory updates
- Retry Logic: Automatic retry on optimistic locking failures (Spring Retry)
- Account Transactions: Complete history of all balance changes (RECHARGE, DEBIT, CREDIT)
- Inventory Transactions: Complete history of all inventory changes (ADD, DEDUCT)
- Reference Tracking: All transactions linked to orders or operations
- Daily Scheduled Job: Automatically reconciles all merchants at 2 AM
- Discrepancy Detection: Compares account balance vs calculated sales value
- Report Generation: Historical reconciliation reports with status (MATCHED/DISCREPANCY)
- Manual Triggers: API endpoints for on-demand reconciliation
- Global Exception Handler: Centralized error handling with structured error responses
- Custom Exceptions: ResourceNotFound, InsufficientBalance, InvalidOperation
- Validation: Jakarta Bean Validation on all request DTOs
The system is designed to be highly configurable and extensible:
- Payment Gateway: Interface-based design allows easy integration of real banking APIs (e.g., https://sandbox.bind.com.ar/apidoc/)
- Configuration: Externalized configuration via
application.ymlwith Spring@ConfigurationProperties - Modular Structure: Clear module boundaries enable future microservice split
- DDD Architecture: Domain-driven design with clear aggregate roots and boundaries
- Event-Driven Hooks: Ready for Kafka integration if needed
- Repository Pattern: Easy to swap implementations or add new data sources
- Integration with real banking API (https://sandbox.bind.com.ar/apidoc/)
- Kafka event-driven architecture
- Multi-currency support
- Advanced reporting and analytics
- WebSocket support for real-time updates
If you encounter database connection errors:
- Ensure Docker is running:
docker ps - Check PostgreSQL container:
docker-compose ps - Verify database is accessible:
docker exec -it transaction-system-db psql -U postgres -d transaction_system - Check connection settings in
application.yml
If port 8080 is already in use:
- Change
server.portinapplication.yml - Or stop the conflicting service:
lsof -i :8080ornetstat -tulpn | grep 8080
If migrations fail:
- Check database connection
- Verify migration files are in
src/main/resources/db/migration/ - Check Flyway logs in application output
- Ensure database is empty or use
baseline-on-migrate: true
If Swagger UI is empty:
- Restart the application (SpringDoc scans at startup)
- Check application logs for errors
- Verify controllers are in
com.mamoru.transactionsystempackage - Access Swagger UI at: http://localhost:8080/swagger-ui.html
If tests fail:
- Ensure Docker is running (for Testcontainers)
- Check test logs for specific errors
- Verify test profile is active:
@ActiveProfiles("test") - For integration tests, ensure PostgreSQL container starts successfully
curl -X POST http://localhost:8080/api/v1/users \
-H "Content-Type: application/json" \
-d '{
"username": "john_doe",
"email": "john@example.com"
}'curl -X POST http://localhost:8080/api/v1/users/1/accounts/recharge \
-H "Content-Type: application/json" \
-d '{
"amount": 1000.00
}'curl -X POST http://localhost:8080/api/v1/merchants \
-H "Content-Type: application/json" \
-d '{
"name": "Tech Store"
}'curl -X POST http://localhost:8080/api/v1/merchants/1/products \
-H "Content-Type: application/json" \
-d '{
"sku": "LAPTOP-001",
"name": "Gaming Laptop",
"price": 999.99
}'curl -X POST http://localhost:8080/api/v1/merchants/1/inventory/products/1/add \
-H "Content-Type: application/json" \
-d '{
"quantity": 10
}'curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-H "X-User-Id: 1" \
-d '{
"merchantId": 1,
"sku": "LAPTOP-001",
"quantity": 1
}'curl -X POST http://localhost:8080/api/v1/reconciliation/merchants/1/runThis is an assessment project for technical evaluation purposes.
For questions or issues, please refer to the assessment requirements document.