A backend service for calculating shipping charges in a B2B e-commerce marketplace. The system determines the nearest warehouse for a seller and calculates shipping costs based on distance, product weight, and delivery speed.
- Java 17
- Spring Boot 3.2.0
- Spring Web - REST API
- Spring Data JPA - Persistence
- Spring Cache - In-memory caching
- H2 Database - In-memory database
- Spring Validation - Request validation
- Customer - Represents a customer with location (latitude, longitude)
- Seller - Represents a seller with location
- Product - Contains product details including weight (used for pricing calculation)
- Warehouse - Represents a warehouse location
- Location - Embeddable containing latitude and longitude coordinates
- Dimension - Embeddable for product dimensions (stored but not used in current pricing logic)
- DeliverySpeed - Enum with
STANDARDandEXPRESSoptions
GET /api/v1/warehouse/nearest?sellerId={sellerId}&productId={productId}
Purpose: Find the nearest warehouse based on seller location.
Note: productId is accepted for API consistency but not used in warehouse selection. Warehouse selection depends only on seller location.
Response:
{
"warehouseId": "789",
"warehouseLocation": {
"latitude": 12.99999,
"longitude": 37.923273
}
}GET /api/v1/shipping-charge?warehouseId={warehouseId}&customerId={customerId}&productId={productId}&deliverySpeed={deliverySpeed}
Purpose: Calculate shipping charge from a specific warehouse to customer.
Response:
{
"shippingCharge": 45.20
}POST /api/v1/shipping-charge/calculate
Content-Type: application/json
{
"sellerId": "123",
"customerId": "456",
"productId": "456",
"deliverySpeed": "standard"
}
Purpose: Complete workflow - finds nearest warehouse and calculates shipping charge.
Response:
{
"shippingCharge": 45.20,
"nearestWarehouse": {
"warehouseId": "789",
"warehouseLocation": {
"latitude": 12.99999,
"longitude": 37.923273
}
}
}Distance between locations is calculated using the Haversine formula, which provides accurate distance measurements between two geographic coordinates.
Based on distance, the system automatically selects the most cost-effective transport mode:
| Distance Range | Transport Mode | Rate |
|---|---|---|
| 0 - 100 km | Mini Van | ₹3/km/kg |
| 100 - 500 km | Truck | ₹2/km/kg |
| 500+ km | Aeroplane | ₹1/km/kg |
Additional charges based on delivery speed preference:
- STANDARD: ₹10 flat surcharge
- EXPRESS: ₹10 + (₹1.2 × product weight in kg)
Total Shipping Charge = (Transport Cost) + (Delivery Speed Surcharge)
where Transport Cost = Distance × Rate × Product Weight
The system uses the Strategy Pattern to handle different transport modes:
MiniVanStrategyTruckStrategyAirplaneStrategy
This design makes it easy to add new transport modes or modify pricing rules without changing core business logic.
- Controllers - Handle HTTP requests/responses, validate input, delegate to services
- Services - Contain business logic (distance calculation, pricing, warehouse selection)
- Repositories - Data access layer using Spring Data JPA
The DeliverySpeed enum includes a fromString() factory method for safe parsing with proper error handling, keeping parsing logic close to the enum definition.
Nearest warehouse lookups are cached using Spring Cache (in-memory ConcurrentMapCache):
- Cache Name:
nearestWarehouseCache - Cache Key:
sellerId - Cache Condition: Results are cached only if non-null (
unless = "#result == null")
Rationale: Warehouse locations are static during application runtime, and distance calculations using the Haversine formula are relatively expensive. Caching significantly improves performance for repeated lookups.
The project includes focused unit tests for core business logic using plain JUnit 5 without Spring context or mocks.
Three service classes are tested to validate business logic and boundary conditions:
Tests nearest warehouse selection algorithm:
- Selects closest warehouse from multiple options
- Handles single warehouse scenario
- Validates error handling for empty/null warehouse lists
Tests transport strategy selection at boundary conditions:
- ≤100 km → MiniVan strategy selected
- 100-500 km → Truck strategy selected
- >500 km → Airplane strategy selected
- Boundary validation at exactly 100 km and 500 km
Tests end-to-end shipping charge calculations:
- STANDARD delivery charge calculation
- EXPRESS delivery charge calculation (validates higher cost)
- Long distance scenarios (>500 km using Airplane)
- Validates weight multiplication in pricing
# Run all tests
mvn test
# Run specific test class
mvn test -Dtest=WarehouseServiceTest
mvn test -Dtest=TransportPricingResolverTest
mvn test -Dtest=ShippingChargeServiceTest- No Mocks: Tests use real service objects to validate actual business logic
- No Spring Context: Plain JUnit tests for fast execution
- Boundary Focus: Tests validate edge cases and boundary conditions
- Business Value: Focus on proving correctness of pricing, distance, and selection logic
- Uses JPA/Hibernate with H2 in-memory database
- Schema is auto-created on startup (
ddl-auto: create-drop)
The DataLoader component (implements CommandLineRunner) pre-loads sample data on application startup:
- Customers: Shree Kirana Store, Andheri Mini Mart
- Sellers: Nestle Seller, Rice Seller, Sugar Seller
- Products: Maggie 500g Packet, Rice Bag 10Kg, Sugar Bag 25kg
- Warehouses: BLR_Warehouse, MUMB_Warehouse
No external database setup is required.
Global exception handling via @RestControllerAdvice provides consistent error responses:
- 404 Not Found - When seller, customer, product, or warehouse is not found
- 400 Bad Request - Validation errors or invalid business logic (e.g., invalid delivery speed)
- 500 Internal Server Error - Unexpected errors
{
"timestamp": "2026-01-30T17:43:21",
"status": 404,
"error": "Not Found",
"message": "Product not found with ID: invalid-id"
}- Single Product per Shipment - Each shipping calculation request handles one product at a time (no cart or multi-product aggregation)
- Static Warehouse Locations - Warehouse locations do not change during application runtime
- Weight-Based Pricing - Product dimensions are stored but not used in pricing calculations
- Product Weight Availability - All products have weight information
- In-Memory Data Sufficient - No persistence required beyond application lifecycle for this assignment scope
- India-Based Locations - All locations use Indian Rupees (₹) for pricing
- Java 17 or higher
- Maven 3.6+
-
Clone the repository:
cd b2b-logistics-engine -
Build the project:
mvn clean package
-
Run the application:
mvn spring-boot:run
Or run the JAR directly:
java -jar target/b2b-logistics-engine-0.0.1-SNAPSHOT.jar
-
Verify the application is running:
curl http://localhost:8080/api/v1/warehouse/nearest?sellerId=123&productId=456
- No database installation needed (H2 in-memory)
- No cache server required (Spring's in-memory cache)
- No additional configuration files needed
The application will start on http://localhost:8080 and load sample data automatically.
curl "http://localhost:8080/api/v1/warehouse/nearest?sellerId=123&productId=456"curl "http://localhost:8080/api/v1/shipping-charge?warehouseId=789&customerId=456&productId=456&deliverySpeed=standard"curl -X POST http://localhost:8080/api/v1/shipping-charge/calculate \
-H "Content-Type: application/json" \
-d '{
"sellerId": "123",
"customerId": "456",
"productId": "456",
"deliverySpeed": "express"
}'