Skip to main content

System Overview

The Product Distribution Dashboard follows a modern three-tier architecture with clear separation between data, business logic, and presentation layers.

Backend Architecture

The backend is built with Spring Boot 3.4.5 and follows a layered architecture pattern.

Core Layers

Controllers

REST API endpoints for frontend communication

Services

Business logic and orchestration

Repositories

Data access using Spring Data JPA

Key Services

The backend contains several specialized services that work together:

DistributionService

Core service responsible for the product distribution algorithm:
@Service
public class DistributionService {
    
    @Value("${distribution.strategy.warehouse-selection:distanceOnlyStrategy}")
    private String strategyName;
    
    @Transactional
    public List<StockAssignment> distributeProducts() {
        loadData();
        List<StockAssignment> assignments = new ArrayList<>();
        List<UnfulfilledDemand> unfulfilledDemands = new ArrayList<>();
        
        for (Store store : stores) {
            processStoreDemand(store, assignments, unfulfilledDemands);
        }
        
        persistResults(assignments, unfulfilledDemands);
        return assignments;
    }
}
Key responsibilities:
  • Load products, stores, and warehouses from JSON sources
  • Execute configurable warehouse selection strategies
  • Calculate adjusted demand based on expected return rates
  • Allocate inventory while respecting capacity constraints
  • Track unfulfilled demands with reasons (stock vs. capacity)
  • Cache eviction for metrics recalculation
The distribution algorithm can be found in /backend/src/main/java/com/productdistribution/backend/services/DistributionService.java:192

JsonDataLoaderService

Handles loading data from external JSON sources:
@Service
public class JsonDataLoaderService implements DataLoaderService {
    
    @Value("${data.products.url}")
    private String productsUrl;
    
    private <T> T loadFromUrl(String url, TypeReference<T> typeReference) {
        String jsonContent = webClient.get()
            .uri(url)
            .retrieve()
            .bodyToMono(String.class)
            .block();
        return objectMapper.readValue(jsonContent, typeReference);
    }
}
Features:
  • Uses Spring WebFlux WebClient for async HTTP calls
  • Configurable data source URLs via application properties
  • Jackson ObjectMapper for JSON deserialization
  • Error handling for network and parsing failures

Other Services

  • MetricsService: Calculates global and detailed metrics with caching
  • GeoDistanceService: Computes Haversine distances between coordinates
  • ProductService, StoreService, WarehouseService: Domain-specific data management
  • StockAssignmentService, UnfulfilledDemandService: Result persistence

Entity Model

The domain model consists of six main entities:

Product

Product catalog with brand and available sizes

Store

Store locations with geolocation, capacity, and demand

Warehouse

Warehouse locations with inventory

ProductItem

Individual product items by size and quantity

StockAssignment

Distribution assignments from warehouses to stores

UnfulfilledDemand

Unmet demands with reasons

Technology Stack

The backend leverages modern Spring Boot capabilities:
  • Spring Data JPA: Repository abstraction and database access
  • Spring Web: RESTful API with @RestController
  • Spring WebFlux: Reactive WebClient for external HTTP calls
  • Spring Cache: Method-level caching for metrics
  • Spring Validation: Request validation with Bean Validation
  • Spring Actuator: Health checks and operational endpoints
  • Flyway: Database migration management
  • Lombok: Reduce boilerplate with annotations
  • MapStruct: Type-safe object mapping
  • HikariCP: High-performance connection pooling

Frontend Architecture

The frontend is built with Angular 18 using standalone components and follows a component-based architecture.

Component Structure

app/
├── app.component.ts              # Root component
├── dashboard-filters/            # Filter controls
├── dashboard-map/                # Leaflet map visualization
├── dashboard-metrics/            # Global metrics display
├── dashboard-metrics-detail/     # Detailed breakdowns
├── dashboard-table/              # Data table with sorting
├── dashboard-tabs/               # Tab navigation
├── services/                     # HTTP services
│   ├── metrics.service.ts
│   ├── product.service.ts
│   ├── stock-assignment.service.ts
│   ├── store.service.ts
│   └── warehouse.service.ts
└── models/                       # TypeScript interfaces

Key Components

DashboardMapComponent

Interactive map visualization using Leaflet:
export class DashboardMapComponent implements AfterViewInit, OnChanges {
  @Input() filters!: DashboardFilters;
  @Input() assignments: StockAssignment[] = [];
  @Input() warehouses: Warehouse[] = [];
  @Input() stores: Store[] = [];
  
  private initMap(): void {
    this.map = this.L.map('map', {
      maxBounds: [[-90, -180], [90, 180]],
      minZoom: 2,
      maxZoom: 19,
    }).setView([40, 0], 2);
    
    this.L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png')
      .addTo(this.map);
  }
}
Features:
  • Marker clustering for warehouses and stores
  • Polyline routes with directional arrows
  • Popup details for stock assignments
  • Hover and click interactions
  • Responsive filtering
The map component implementation is in /frontend/src/app/dashboard-map/dashboard-map.component.ts:1

Services

Angular services handle all HTTP communication with the backend:
  • MetricsService: Fetch global and detailed metrics
  • StockAssignmentService: Query stock assignments with filtering
  • ProductService, StoreService, WarehouseService: Domain data retrieval
All services use RxJS Observables for reactive data handling.

Frontend Technology Stack

  • Angular 18: Modern framework with standalone components
  • TypeScript: Type-safe development
  • Leaflet: Interactive mapping library
  • Leaflet.markercluster: Marker clustering
  • Leaflet-polylinedecorator: Directional arrows on routes
  • Chart.js / ng2-charts: Data visualization
  • ng-select: Enhanced dropdown controls
  • RxJS: Reactive programming with Observables
  • ESLint & Prettier: Code quality and formatting

Database Schema

The PostgreSQL database schema is managed by Flyway migrations and consists of six main tables.

Schema Diagram

Table Descriptions

products stores the product catalog with unique IDs and brand associations.product_sizes is a join table storing the available sizes for each product.Migration: V1__Create_products_table.sql
Stores have geolocation (latitude, longitude), capacity constraints, and expected return rates.The remaining_capacity field is dynamically updated during distribution to track available space.Migration: V2__Create_stores_table.sql
Warehouses are simpler entities with just geolocation and country information.Their inventory is tracked in the product_items table.Migration: V3__Create_warehouses_table.sql
Represents individual product inventory items, either in warehouses or demanded by stores.Each item has a product ID, size, quantity, and either a store_id (demand) or warehouse_id (stock).Includes indexes on store_id, warehouse_id, and product_id, size for query performance.Migration: V4__Create_product_items_table.sql
The result of the distribution algorithm - represents the decision to send specific quantities from a warehouse to a store.Includes the calculated distance in kilometers for analytics.Multiple composite indexes support efficient querying by warehouse, store, product, and distance.Migration: V5__Create_stock_assignments_table.sql
Tracks demands that couldn’t be fulfilled, along with the reason:
  • STOCK_SHORTAGE: No warehouse had available inventory
  • CAPACITY_SHORTAGE: Store reached maximum capacity
Essential for identifying supply chain gaps and planning improvements.Migration: V6__Create_unfulfilled_demands_table.sql

Data Flow

The system processes data through several distinct phases:
1

Initialization

On application startup:
  1. Flyway runs database migrations to create/update schema
  2. Spring Boot initializes all beans and services
  3. Backend is ready to accept requests
2

Data Loading

When triggered (manually or via scheduler):
  1. JsonDataLoaderService fetches JSON from configured URLs
  2. Jackson deserializes JSON into entity objects
  3. Services persist data to PostgreSQL via JPA repositories
  4. Products, stores (with demand), and warehouses (with stock) are loaded
3

Distribution Execution

The DistributionService.distributeProducts() method:
  1. Loads fresh data from JSON sources
  2. For each store and each demand item:
    • Calculates adjusted demand based on return rate
    • Selects candidate warehouses using configured strategy
    • Allocates stock while respecting capacity constraints
    • Creates StockAssignment records
    • Tracks UnfulfilledDemand for shortages
  3. Persists all assignments and unfulfilled demands
  4. Updates store remaining capacity
  5. Evicts metrics cache to trigger recalculation
4

Metrics Calculation

The MetricsService computes:
  • Global metrics: Total assignments, fulfillment rate, average distance, capacity utilization
  • Detailed metrics: Per-warehouse and per-store breakdowns
  • Results are cached for performance
5

Frontend Rendering

The Angular application:
  1. Calls REST APIs to fetch assignments, warehouses, stores, and metrics
  2. Renders interactive map with markers and routes
  3. Displays data tables with sorting and filtering
  4. Shows real-time metrics dashboards
  5. Responds to user filter changes reactively

Distribution Algorithm

The core distribution algorithm operates as follows:

Pseudocode

for each store:
    for each product demand item:
        adjusted_demand = original_demand * (1 + expected_return_rate)
        
        warehouses = selectWarehouses(store, product, size)
        
        for each warehouse in warehouses:
            if warehouse has stock:
                quantity_to_send = min(
                    adjusted_demand,
                    warehouse_stock,
                    store_capacity_remaining
                )
                
                if quantity_to_send > 0 and store.tryAllocateCapacity(quantity_to_send):
                    create stock_assignment
                    reduce warehouse_stock
                    reduce adjusted_demand
                
                if adjusted_demand == 0 or store_capacity == 0:
                    break
        
        if adjusted_demand > 0:
            reason = store_capacity == 0 ? CAPACITY_SHORTAGE : STOCK_SHORTAGE
            create unfulfilled_demand

Warehouse Selection Strategies

The system supports pluggable warehouse selection strategies:
  • DistanceOnlyStrategy (default): Selects warehouses sorted by geographic distance
  • Custom strategies can be implemented by extending WarehouseSelectionStrategy
The strategy is configured via:
distribution.strategy.warehouse-selection=distanceOnlyStrategy

Caching Strategy

The backend uses Spring Cache for performance optimization:
  • Metrics caching: Global and detailed metrics are cached to avoid expensive recalculations
  • Cache eviction: Automatically cleared when distribution runs (via @CacheEvict)
  • Cache names: globalMetrics, detailedMetrics

API Endpoints

The backend exposes RESTful endpoints (examples):
  • GET /api/stock-assignments - Retrieve all stock assignments
  • GET /api/stock-assignments?storeId={id} - Filter by store
  • GET /api/stock-assignments?warehouseId={id} - Filter by warehouse
  • GET /api/metrics/global - Get global metrics
  • GET /api/metrics/detailed - Get detailed breakdowns
  • GET /api/stores - List all stores
  • GET /api/warehouses - List all warehouses
  • GET /api/products - List all products
  • POST /api/stock-assignments/distribute - Trigger distribution manually
The exact endpoint paths depend on the controller implementations. Check the source code for the complete API reference.

Deployment Architecture

The application supports two deployment modes:

Local Development (Docker Compose)

  • All services run in Docker containers on a single machine
  • PostgreSQL data persists in Docker volumes
  • Frontend served via Angular dev server (port 4200)
  • Backend runs with Spring Boot DevTools for hot reload

Production (Render)

  • Backend: Deployed as a Docker service with prod profile
  • Frontend: Built as static site and served from CDN
  • Database: External PostgreSQL (Microsoft Azure)
  • Health checks: /actuator/health endpoint
  • Environment variables: Database connection, data URLs, scheduler config
Configuration is managed via render.yaml in the repository root.

Performance Considerations

Database Indexing

Strategic indexes on foreign keys and frequently queried columns for fast lookups

Caching

Metrics cached to avoid expensive aggregation queries on every request

Connection Pooling

HikariCP optimized for high throughput with configurable pool sizes

Batch Processing

Batch inserts for stock assignments and unfulfilled demands

What’s Next?

API Reference

Explore the REST API endpoints and data models

Configuration Guide

Learn how to configure data sources, strategies, and deployment

Backend Development

Guidelines for backend development, testing, and services

Frontend Development

Guidelines for frontend development and components

Deployment

Deploy to production environments like Render or AWS

Build docs developers (and LLMs) love