Skip to main content

Overview

The backend uses a layered architecture with clear separation of concerns:
Controllers → Services → Repositories → Database
     ↓           ↓            ↓
   DTOs    ← Mappers ←    Entities

Key Services

DistributionService

Core service that orchestrates product distribution from warehouses to stores. Location: services/DistributionService.java:24 Key Features:
  • Strategy pattern for warehouse selection
  • Transaction management with @Transactional
  • Cache eviction with @CacheEvict
  • Demand fulfillment algorithm
Main Method:
@CacheEvict(value = {"globalMetrics", "detailedMetrics"}, allEntries = true)
@Transactional
public List<StockAssignment> distributeProducts()
Algorithm Flow:
  1. Load data from JSON sources
  2. For each store, process demand items
  3. Select warehouses using configured strategy
  4. Calculate quantities considering capacity constraints
  5. Create assignments and track unfulfilled demands
  6. Persist results to database
Configuration: Set the warehouse selection strategy in application.properties:
distribution.strategy.warehouse-selection=distanceWithToleranceStrategy
Available strategies:
  • distanceOnlyStrategy - Select nearest warehouse
  • distanceWithToleranceStrategy - Consider nearby warehouses within tolerance

ProductService

Manages product data and CRUD operations. Location: services/ProductService.java:16 Key Methods:
// Load products from external JSON source
public List<Product> loadProductsFromJson()

// Refresh database with latest JSON data
@Transactional
public List<Product> refreshProductsFromJson()

// Get all products sorted by ID
public List<Product> getAllProducts()

// Get single product by ID
public Product getProductById(String id)
Exception Handling: Throws ResourceNotFoundException when product not found:
productRepository.findById(id)
    .orElseThrow(() -> new ResourceNotFoundException("Product", id));

StoreService

Manages store data and operations. Location: services/StoreService.java:16 Similar pattern to ProductService:
  • Load stores from JSON
  • Refresh database
  • CRUD operations
  • Transaction management with EntityManager.clear()

WarehouseService

Manages warehouse data and inventory. Location: services/WarehouseService.java:16 Key Operations:
  • Load warehouses from external JSON
  • Manage warehouse inventory
  • Track stock availability
  • Handle capacity constraints

MetricsService

Calculates distribution metrics and performance indicators. Location: services/MetricsService.java:17 Caching Strategy:
@Cacheable("globalMetrics")
public GlobalMetricsDTO calculateGlobalMetrics()

@Cacheable(value = "detailedMetrics", 
          key = "#criteria.warehouseId + '_' + #criteria.storeId + '_' + #criteria.productId")
public DetailedMetricsDTO calculateDetailedMetrics(StockAssignmentCriteria criteria)
Efficiency Score Calculation: Weighted formula combining multiple factors:
private static final double FULFILLMENT_RATE_WEIGHT = 0.7;
private static final double DISTANCE_WEIGHT = 0.2;
private static final double CAPACITY_WEIGHT = 0.1;

private Integer calculateEfficiencyScore(
    Double fulfillmentRate, 
    Double averageDistance, 
    Double capacityUtilization
) {
    double normalizedDistance = Math.max(0, 1 - (averageDistance / MAX_DISTANCE_FOR_NORMALIZATION));
    double normalizedDistancePercent = normalizedDistance * 100.0;
    
    double score = (fulfillmentRate * FULFILLMENT_RATE_WEIGHT) + 
                  (normalizedDistancePercent * DISTANCE_WEIGHT) + 
                  (capacityUtilization * CAPACITY_WEIGHT);
    
    return Math.max(0, Math.min(100, (int) Math.round(score)));
}

Repository Pattern

All services use Spring Data JPA repositories for database access.

Standard Repository Interface

@Repository
public interface ProductRepository extends JpaRepository<Product, String> {
    @Modifying
    @Query(value = "TRUNCATE TABLE products RESTART IDENTITY CASCADE", nativeQuery = true)
    void truncateAll();
}

Custom Queries

Repositories can define custom queries using:
  • Method name conventions
  • @Query annotations
  • Native SQL queries
  • Criteria API
Example from MetricsRepository:
@Query("""SELECT 
    COUNT(sa) as totalShipments,
    COALESCE(SUM(sa.quantity), 0) as fulfilledUnits,
    COALESCE(SUM(ud.quantity), 0) as unfulfilledUnits,
    COALESCE(AVG(sa.distance), 0.0) as averageDistance
    FROM StockAssignment sa, UnfulfilledDemand ud""")
GlobalMetricsProjection getGlobalMetrics();

MapStruct DTOs

MapStruct automatically generates mapping code between entities and DTOs.

Mapper Interface

@Mapper(componentModel = "spring")
public interface ProductMapper {
    ProductDTO toDTO(Product entity);
    Product toEntity(ProductDTO dto);
    List<ProductDTO> toDTOList(List<Product> entities);
    List<Product> toEntityList(List<ProductDTO> dtos);
}

Usage in Controllers

@RestController
@RequestMapping("/api/products")
public class ProductController {
    private final ProductService productService;
    private final ProductMapper productMapper;
    
    @GetMapping
    public List<ProductDTO> getAllProducts() {
        List<Product> products = productService.getAllProducts();
        return productMapper.toDTOList(products);
    }
}

Benefits

  • Type-safe - Compile-time validation
  • Performance - No reflection, generated code
  • Maintainable - Single source of truth
  • Flexible - Custom mapping methods supported

Generated Code

MapStruct generates implementation classes during compilation:
// Generated by MapStruct
@Component
public class ProductMapperImpl implements ProductMapper {
    @Override
    public ProductDTO toDTO(Product entity) {
        if (entity == null) return null;
        
        return new ProductDTO(
            entity.getId(),
            entity.getBrandId(),
            entity.getSizes()
        );
    }
}

Caching Strategy

Spring’s caching abstraction improves performance for expensive operations.

Configuration

In application.properties:
spring.cache.type=simple
spring.cache.cache-names=globalMetrics,detailedMetrics

Cache Annotations

@Cacheable - Cache method results:
@Cacheable("globalMetrics")
public GlobalMetricsDTO calculateGlobalMetrics() {
    // Expensive calculation only runs once
    // Subsequent calls return cached value
}
@CacheEvict - Clear cache when data changes:
@CacheEvict(value = {"globalMetrics", "detailedMetrics"}, allEntries = true)
@Transactional
public List<StockAssignment> distributeProducts() {
    // Cache cleared after distribution
}
@CachePut - Update cache:
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
    return productRepository.save(product);
}

Cache Key Strategy

For complex cache keys, use SpEL expressions:
@Cacheable(value = "detailedMetrics", 
          key = "#criteria.warehouseId + '_' + #criteria.storeId + '_' + #criteria.productId")
public DetailedMetricsDTO calculateDetailedMetrics(StockAssignmentCriteria criteria)

Transaction Management

Spring manages transactions declaratively using @Transactional.

Service-Level Transactions

@Service
public class ProductService {
    private final EntityManager entityManager;
    
    @Transactional
    public List<Product> refreshProductsFromJson() {
        deleteAll();
        List<Product> products = loadProductsFromJson();
        addAll(products);
        return products;
    }
    
    public void deleteAll() {
        productRepository.truncateAll();
        entityManager.clear(); // Clear persistence context
    }
}

Transaction Propagation

Spring supports different propagation behaviors:
@Transactional(propagation = Propagation.REQUIRED) // Default
@Transactional(propagation = Propagation.REQUIRES_NEW) // New transaction
@Transactional(propagation = Propagation.MANDATORY) // Must be in transaction

Rollback Rules

By default, transactions rollback on runtime exceptions:
@Transactional(rollbackFor = Exception.class)
public void riskyOperation() throws Exception {
    // Rolls back on any exception
}

@Transactional(noRollbackFor = ValidationException.class)
public void validateAndSave() {
    // Doesn't rollback on ValidationException
}

Dependency Injection

Services use constructor injection (recommended over field injection):
@Service
public class ProductService {
    private final ProductRepository productRepository;
    private final DataLoaderService dataLoaderService;
    private final EntityManager entityManager;

    @Autowired // Optional in Spring 4.3+
    public ProductService(
        ProductRepository productRepository, 
        DataLoaderService dataLoaderService, 
        EntityManager entityManager
    ) {
        this.productRepository = productRepository;
        this.dataLoaderService = dataLoaderService;
        this.entityManager = entityManager;
    }
}
Benefits:
  • Immutable dependencies
  • Easier testing (can use constructor without Spring)
  • Clear required dependencies

Exception Handling

Custom exceptions provide meaningful error messages:
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String resourceType, String id) {
        super(String.format("%s with id '%s' not found", resourceType, id));
    }
}

public class ConfigurationException extends RuntimeException {
    public ConfigurationException(String message, Throwable cause) {
        super(message, cause);
    }
}
Usage:
public Product getProductById(String id) {
    return productRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Product", id));
}
Global exception handler in controllers translates exceptions to HTTP responses.

Best Practices

  1. Keep services focused - Single responsibility principle
  2. Use constructor injection - Better testability
  3. Transaction boundaries - Mark transactional methods appropriately
  4. Cache strategically - Only cache expensive operations
  5. Clear cache on updates - Prevent stale data
  6. Handle exceptions - Provide meaningful error messages
  7. Log appropriately - Info for important operations, debug for details
  8. Entity manager awareness - Clear context after bulk operations

Build docs developers (and LLMs) love