Product Catalog Service - Domain Architecture & Business Capabilities

Service Overview

The Product Catalog Service is the canonical source of truth for all product master data across the fulfillment ecosystem. It manages product information including SKUs, descriptions, dimensions, weights, and special handling requirements such as hazardous materials.

Architecture Pattern: Hexagonal Architecture with Domain-Driven Design (DDD) Technology Stack: Spring Boot 3, Spring Data MongoDB, Spring Kafka, CQRS Integration Pattern: Event-Driven Architecture with Published Language


Domain Model & Bounded Context

Bounded Context: Product Catalog

The Product Catalog bounded context is responsible for maintaining the master data for all products that flow through the fulfillment network.

Context Boundaries

Responsibilities (What’s IN):

External Dependencies (What’s OUT):

Ubiquitous Language

Core Domain Terms:


Subdomain Classification

Core Domain: Product Master Data Management

Strategic Importance: MEDIUM - Supporting domain but critical for operations

This is a supporting domain that enables fulfillment operations through:

Subdomains:

1. Product Information Management (Supporting)

2. Dimensional Data Management (Supporting)

3. Hazmat Compliance Management (Supporting)

4. Product Data Synchronization (Generic)


Domain Model

Aggregates

1. Product (Aggregate Root)

Description: The central aggregate representing a product with all its attributes and characteristics.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@AggregateRoot
public class Product {
    private String sku; // Aggregate ID
    private String name;
    private String description;
    private Dimensions dimensions;
    private Weight weight;
    private ProductAttributes attributes;
    private boolean archived;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private int version; // Optimistic locking

    // Business methods
    public void updateDimensions(Dimensions newDimensions);
    public void updateWeight(Weight newWeight);
    public void updateAttributes(ProductAttributes newAttributes);
    public void archive();
    public void restore();
    public boolean isHazmat();
    public boolean isFragile();
    public boolean isOversized(Dimensions threshold);

    // Invariants
    private void ensureSkuIsValid();
    private void ensureItemDimensionsNotExceedPackage();
    private void ensureHazmatDataComplete();
    private void ensureWeightIsPositive();
}

Invariants:

Domain Events:


Value Objects

Dimensions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@ValueObject
public class Dimensions {
    private ItemDimension item;
    private PackageDimension package_;

    public Volume getItemVolume();
    public Volume getPackageVolume();
    public double getPackageEfficiency(); // item volume / package volume
    public boolean itemFitsInPackage();
    public DimensionalWeight calculateDimensionalWeight(int dimFactor);
}

@ValueObject
public class ItemDimension {
    private BigDecimal length;
    private BigDecimal width;
    private BigDecimal height;
    private DimensionUnit unit; // INCHES, CENTIMETERS

    public Volume calculateVolume();
    public boolean fitsWithin(PackageDimension package_);
}

@ValueObject
public class PackageDimension {
    private BigDecimal length;
    private BigDecimal width;
    private BigDecimal height;
    private DimensionUnit unit;

    public Volume calculateVolume();
    public boolean canAccommodate(ItemDimension item);
}

Weight

1
2
3
4
5
6
7
8
9
@ValueObject
public class Weight {
    private BigDecimal value;
    private WeightUnit unit; // POUNDS, KILOGRAMS, OUNCES

    public Weight convertTo(WeightUnit targetUnit);
    public boolean isHeavy(Weight threshold);
    public boolean exceedsLimit(Weight maxWeight);
}

ProductAttributes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ValueObject
public class ProductAttributes {
    private boolean fragile;
    private boolean perishable;
    private boolean highValue;
    private boolean oversized;
    private boolean batteryPowered;
    private boolean liquid;
    private HazmatInfo hazmat; // null if not hazmat

    public boolean requiresSpecialHandling();
    public Set<HandlingRequirement> getHandlingRequirements();
    public boolean hasShippingRestrictions();
}

HazmatInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ValueObject
public class HazmatInfo {
    private String unNumber; // UN1266
    private String hazardClass; // 3 (Flammable liquids)
    private String packingGroup; // II
    private String properShippingName;
    private boolean airEligible;
    private boolean groundOnly;
    private List<String> specialInstructions;

    public boolean isComplete();
    public boolean canShipVia(ShippingMethod method);
    public String formatForShippingLabel();
}

Domain Services

ProductValidationService

Responsibility: Validate product data for consistency and business rules.

1
2
3
4
5
6
7
8
9
10
@DomainService
public class ProductValidationService {

    public ValidationResult validate(Product product);

    private ValidationResult validateSKU(String sku);
    private ValidationResult validateDimensions(Dimensions dimensions);
    private ValidationResult validateWeight(Weight weight);
    private ValidationResult validateHazmatInfo(HazmatInfo hazmat);
}

DimensionalWeightCalculator

Responsibility: Calculate dimensional weight for shipping cost estimation.

1
2
3
4
5
6
7
8
9
10
11
12
13
@DomainService
public class DimensionalWeightCalculator {

    public DimensionalWeight calculate(
        PackageDimension dimensions,
        int dimFactor // carrier-specific: FedEx=139, UPS=139
    );

    public Weight getChargeableWeight(
        Weight actualWeight,
        DimensionalWeight dimWeight
    ); // Returns greater of actual or dimensional
}

Application Layer

Ports (Interfaces)

Input Ports (Use Cases - CQRS Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Commands
public interface CreateProductCommand {
    Product execute(CreateProductRequest request);
}

public interface UpdateProductCommand {
    Product execute(UpdateProductRequest request);
}

public interface ArchiveProductCommand {
    void execute(String sku);
}

public interface RestoreProductCommand {
    void execute(String sku);
}

// Queries (Separate query services for CQRS)
public interface GetProductQuery {
    Product execute(String sku);
}

public interface ListProductsQuery {
    PagedResult<Product> execute(int offset, int limit, boolean includeArchived);
}

public interface SearchProductsQuery {
    List<Product> execute(String searchTerm);
}

public interface GetProductDimensionsQuery {
    Dimensions execute(String sku);
}

Output Ports (Dependencies)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Repository ports
public interface ProductRepository {
    Optional<Product> findBySku(String sku);
    List<Product> findAllActive(Pageable pageable);
    List<Product> findBySkus(List<String> skus);
    void save(Product product);
    void delete(String sku);
    boolean existsBySku(String sku);
}

// Event publishing
public interface EventPublisher {
    void publish(DomainEvent event);
}

Infrastructure Layer

Adapters

Inbound Adapters

REST Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<ProductResponse> createProduct(
        @Valid @RequestBody CreateProductRequest request
    );

    @GetMapping("/{sku}")
    public ResponseEntity<ProductResponse> getProduct(
        @PathVariable String sku
    );

    @GetMapping
    public ResponseEntity<PagedProductResponse> listProducts(
        @RequestParam(defaultValue = "0") int offset,
        @RequestParam(defaultValue = "20") int limit,
        @RequestParam(defaultValue = "false") boolean includeArchived
    );

    @PutMapping("/{sku}")
    public ResponseEntity<ProductResponse> updateProduct(
        @PathVariable String sku,
        @Valid @RequestBody UpdateProductRequest request
    );

    @DeleteMapping("/{sku}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public ResponseEntity<Void> archiveProduct(@PathVariable String sku);

    @PostMapping("/{sku}/restore")
    public ResponseEntity<ProductResponse> restoreProduct(@PathVariable String sku);
}

Outbound Adapters

MongoDB Adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Repository
public class MongoProductRepository implements ProductRepository {

    private final MongoTemplate mongoTemplate;

    @Override
    public Optional<Product> findBySku(String sku) {
        return Optional.ofNullable(
            mongoTemplate.findById(sku, Product.class)
        );
    }

    @Override
    public List<Product> findAllActive(Pageable pageable) {
        Query query = new Query(Criteria.where("archived").is(false))
            .with(pageable);
        return mongoTemplate.find(query, Product.class);
    }

    @Override
    public void save(Product product) {
        mongoTemplate.save(product);
    }
}

Kafka Event Publisher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class KafkaProductEventPublisher implements EventPublisher {

    private static final String TOPIC = "product.catalog.v1.events";
    private final KafkaTemplate<String, CloudEvent> kafkaTemplate;

    @Override
    public void publish(DomainEvent event) {
        CloudEvent cloudEvent = CloudEventBuilder.v1()
            .withId(event.getId())
            .withType(event.getType())
            .withSource(URI.create("product-catalog-service"))
            .withData(event.toJson().getBytes())
            .build();

        kafkaTemplate.send(TOPIC, cloudEvent);
    }
}

Business Capabilities

L1: Product Master Data Management

Canonical source of truth for all product information used across inventory, cartonization, warehouse operations, and shipment services.


L2: Product Information Management

L3.1: Product Registration (Create)

L3.2: Product Retrieval (Read)

L3.3: Product Update

L3.4: Product Archival & Deletion


L2: Dimensional Data Management

L3.5: Item Dimension Management

L3.6: Package Dimension Management

L3.7: Weight Management

L3.8: Dimensional Validation


L2: Special Handling & Attributes

L3.9: Hazardous Materials (Hazmat) Management

L3.10: Product Attributes Management


L2: Product Data Synchronization

L3.11: Product Event Publishing

L3.12: Bulk Product Export


L2: Data Quality & Governance

L3.13: Data Validation

L3.14: SKU Format Validation

L3.15: Duplicate Detection


Integration Patterns

Context Mapping

Inventory (Downstream - Published Language)

Cartonization (Downstream - Published Language)

Warehouse Operations (Downstream - Published Language)

Shipment & Transportation (Downstream - Published Language)


Event Schemas

ProductCreatedEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
  "specversion": "1.0",
  "type": "com.paklog.product.created",
  "source": "product-catalog-service",
  "id": "evt-prod-12345",
  "time": "2025-10-18T10:30:00Z",
  "datacontenttype": "application/json",
  "data": {
    "sku": "PROD-12345",
    "name": "Widget Pro",
    "description": "Premium widget with advanced features",
    "dimensions": {
      "item": {
        "length": 10.0,
        "width": 5.0,
        "height": 3.0,
        "unit": "INCHES"
      },
      "package": {
        "length": 12.0,
        "width": 7.0,
        "height": 5.0,
        "unit": "INCHES"
      }
    },
    "weight": {
      "value": 2.5,
      "unit": "POUNDS"
    },
    "attributes": {
      "fragile": false,
      "hazmat": false
    },
    "createdAt": "2025-10-18T10:30:00Z"
  }
}

ProductUpdatedEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "specversion": "1.0",
  "type": "com.paklog.product.updated",
  "source": "product-catalog-service",
  "id": "evt-prod-update-456",
  "time": "2025-10-18T11:00:00Z",
  "datacontenttype": "application/json",
  "data": {
    "sku": "PROD-12345",
    "changes": {
      "dimensions": {
        "before": {"item": {"length": 10.0, "width": 5.0, "height": 3.0}},
        "after": {"item": {"length": 11.0, "width": 5.0, "height": 3.0}}
      }
    },
    "updatedAt": "2025-10-18T11:00:00Z"
  }
}

Quality Attributes

Data Quality

Performance

Availability


Summary

The Product Catalog Service is a well-defined bounded context:

Business Impact: Supports millions of SKUs, >95% data quality, <50ms query latency, >90% cache hit rate, real-time event propagation.