Skip to content

4xush/b2b-logistics-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 

Repository files navigation

E-Commerce Shipping Charge Estimator

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.

Tech Stack

  • 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

Domain Model

Core Entities

  • 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

Value Objects

  • Location - Embeddable containing latitude and longitude coordinates
  • Dimension - Embeddable for product dimensions (stored but not used in current pricing logic)
  • DeliverySpeed - Enum with STANDARD and EXPRESS options

APIs Implemented

1. Get Nearest Warehouse

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
  }
}

2. Calculate Shipping Charge

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
}

3. End-to-End Calculation

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
    }
  }
}

Pricing Logic

Distance Calculation

Distance between locations is calculated using the Haversine formula, which provides accurate distance measurements between two geographic coordinates.

Transport Mode Selection

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

Delivery Speed Surcharge

Additional charges based on delivery speed preference:

  • STANDARD: ₹10 flat surcharge
  • EXPRESS: ₹10 + (₹1.2 × product weight in kg)

Final Calculation

Total Shipping Charge = (Transport Cost) + (Delivery Speed Surcharge)

where Transport Cost = Distance × Rate × Product Weight

Design Decisions

Strategy Pattern for Transport Pricing

The system uses the Strategy Pattern to handle different transport modes:

  • MiniVanStrategy
  • TruckStrategy
  • AirplaneStrategy

This design makes it easy to add new transport modes or modify pricing rules without changing core business logic.

Separation of Concerns

  • 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

Enum Factory Pattern

The DeliverySpeed enum includes a fromString() factory method for safe parsing with proper error handling, keeping parsing logic close to the enum definition.

Caching

Warehouse Lookup Caching

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.

Testing

The project includes focused unit tests for core business logic using plain JUnit 5 without Spring context or mocks.

Test Coverage

Three service classes are tested to validate business logic and boundary conditions:

1. WarehouseServiceTest

Tests nearest warehouse selection algorithm:

  • Selects closest warehouse from multiple options
  • Handles single warehouse scenario
  • Validates error handling for empty/null warehouse lists

2. TransportPricingResolverTest

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

3. ShippingChargeServiceTest

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

Running Tests

# Run all tests
mvn test

# Run specific test class
mvn test -Dtest=WarehouseServiceTest
mvn test -Dtest=TransportPricingResolverTest
mvn test -Dtest=ShippingChargeServiceTest

Testing Philosophy

  • 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

Data Storage

H2 In-Memory Database

  • Uses JPA/Hibernate with H2 in-memory database
  • Schema is auto-created on startup (ddl-auto: create-drop)

Sample Data Loading

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.

Error Handling

Global exception handling via @RestControllerAdvice provides consistent error responses:

HTTP Status Codes

  • 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

Error Response Format

{
  "timestamp": "2026-01-30T17:43:21",
  "status": 404,
  "error": "Not Found",
  "message": "Product not found with ID: invalid-id"
}

Assumptions

  1. Single Product per Shipment - Each shipping calculation request handles one product at a time (no cart or multi-product aggregation)
  2. Static Warehouse Locations - Warehouse locations do not change during application runtime
  3. Weight-Based Pricing - Product dimensions are stored but not used in pricing calculations
  4. Product Weight Availability - All products have weight information
  5. In-Memory Data Sufficient - No persistence required beyond application lifecycle for this assignment scope
  6. India-Based Locations - All locations use Indian Rupees (₹) for pricing

How to Run

Prerequisites

  • Java 17 or higher
  • Maven 3.6+

Steps

  1. Clone the repository:

    cd b2b-logistics-engine
  2. Build the project:

    mvn clean package
  3. Run the application:

    mvn spring-boot:run

    Or run the JAR directly:

    java -jar target/b2b-logistics-engine-0.0.1-SNAPSHOT.jar
  4. Verify the application is running:

    curl http://localhost:8080/api/v1/warehouse/nearest?sellerId=123&productId=456

No External Dependencies Required

  • 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.

Sample API Requests

Find Nearest Warehouse

curl "http://localhost:8080/api/v1/warehouse/nearest?sellerId=123&productId=456"

Calculate Shipping Charge

curl "http://localhost:8080/api/v1/shipping-charge?warehouseId=789&customerId=456&productId=456&deliverySpeed=standard"

End-to-End Calculation

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"
  }'

About

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.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages