The Inventory Service is a mission-critical Core Bounded Context within the PakLog fulfillment platform that maintains real-time inventory accuracy across multiple warehouses and locations. Built with Event Sourcing and CQRS patterns, it serves as the single source of truth for inventory availability, reservations, and movements throughout the supply chain ecosystem.
Strategic Importance: CRITICAL - Zero tolerance for inventory discrepancies Architecture Pattern: Event Sourcing + CQRS with Hexagonal Architecture Technology Stack: Java 21, Spring Boot 3.2, PostgreSQL, Apache Kafka, Redis Domain Complexity: HIGH - Complex state management with concurrent operations
Core Purpose: Authoritative source for all inventory positions, movements, reservations, and availability across the entire fulfillment network with real-time accuracy and multi-warehouse support.
Responsibilities (Whatβs IN the Context):
External Dependencies (Whatβs OUT of the Context):
The following terms define the common language used within this bounded context:
| Term | Definition | Business Context |
|---|---|---|
| Stock Position | Current quantity of inventory at a specific location | Core tracking unit |
| Available to Promise (ATP) | Inventory available for new orders | Sales enablement |
| Reservation | Temporary hold on inventory for an order | Order fulfillment |
| Allocation | Permanent assignment of inventory to an order | Fulfillment execution |
| Stock Movement | Any change in inventory quantity or location | Audit trail |
| Cycle Count | Periodic counting of subset of inventory | Accuracy maintenance |
| Adjustment | Manual correction to inventory levels | Error correction |
| Reorder Point | Minimum inventory level triggering replenishment | Stock optimization |
| Safety Stock | Buffer inventory to prevent stockouts | Risk mitigation |
| Inventory Turn | Rate at which inventory is sold and replaced | Performance metric |
| Aging Bucket | Time-based categorization of inventory | Obsolescence tracking |
| Stock Keeping Unit (SKU) | Unique identifier for a product variant | Product identification |
| Lot/Batch | Group of items produced or received together | Traceability unit |
| Serial Number | Unique identifier for individual item | Item-level tracking |
graph TB
subgraph "Inventory Bounded Context"
Core1[Core: Real-time Inventory Tracking]
Core2[Core: Reservation & Allocation]
Support1[Supporting: Cycle Counting]
Support2[Supporting: Inventory Valuation]
Support3[Supporting: Reorder Management]
Generic1[Generic: Event Publishing]
Generic2[Generic: Reporting]
end
Core1 --> Core2
Core1 --> Support1
Core1 --> Support2
Core2 --> Support3
Classification: CORE DOMAIN Strategic Value: CRITICAL - Foundation for all fulfillment operations Investment Priority: HIGHEST - Zero tolerance for inaccuracy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@DomainService
public class InventoryTrackingService {
public StockPosition recordReceipt(
SKU sku,
WarehouseLocation location,
Quantity quantity,
ReceiptMetadata metadata
);
public StockPosition recordMovement(
SKU sku,
WarehouseLocation from,
WarehouseLocation to,
Quantity quantity
);
public StockPosition adjustInventory(
SKU sku,
WarehouseLocation location,
AdjustmentReason reason,
Quantity adjustment
);
}
Classification: CORE DOMAIN Strategic Value: CRITICAL - Enables order fulfillment Investment Priority: HIGHEST - Direct revenue impact
Classification: SUPPORTING DOMAIN Strategic Value: HIGH - Maintains accuracy Investment Priority: MEDIUM - Essential but not differentiating
Classification: SUPPORTING DOMAIN Strategic Value: MEDIUM - Financial reporting requirement Investment Priority: MEDIUM - Compliance necessity
Classification: GENERIC DOMAIN Strategic Value: LOW - Standard infrastructure Investment Priority: LOW - Use platform capabilities
classDiagram
class InventoryItem {
<<Aggregate Root>>
-InventoryItemId id
-SKU sku
-WarehouseLocation location
-StockPosition position
-List~Reservation~ reservations
-List~InventoryEvent~ events
-LocalDateTime lastModified
+receive(quantity, metadata)
+reserve(quantity, orderId, expiry)
+allocate(reservationId)
+release(reservationId)
+adjust(quantity, reason)
+transfer(toLocation, quantity)
+getAvailableToPromise()
}
class StockPosition {
<<Value Object>>
-Quantity onHand
-Quantity reserved
-Quantity allocated
-Quantity inTransit
-Quantity onHold
+available()
+total()
}
class Reservation {
<<Entity>>
-ReservationId id
-OrderId orderId
-Quantity quantity
-ReservationType type
-LocalDateTime createdAt
-LocalDateTime expiresAt
-ReservationStatus status
+expire()
+convert()
+cancel()
}
class InventoryMovement {
<<Entity>>
-MovementId id
-MovementType type
-SKU sku
-WarehouseLocation fromLocation
-WarehouseLocation toLocation
-Quantity quantity
-MovementStatus status
-LocalDateTime createdAt
+complete()
+cancel()
}
InventoryItem --> StockPosition
InventoryItem --> Reservation
classDiagram
class SKU {
<<Value Object>>
-String value
-ProductCategory category
+validate()
+equals()
}
class WarehouseLocation {
<<Value Object>>
-WarehouseId warehouse
-Zone zone
-Aisle aisle
-Bay bay
-Shelf shelf
-Bin bin
+toString()
+isPickable()
}
class Quantity {
<<Value Object>>
-BigDecimal value
-UnitOfMeasure unit
+add()
+subtract()
+isNegative()
+isZero()
}
class LotNumber {
<<Value Object>>
-String value
-LocalDate manufactureDate
-LocalDate expirationDate
+isExpired()
+daysUntilExpiration()
}
class SerialNumber {
<<Value Object>>
-String value
-String manufacturer
+validate()
}
| Event | Trigger | Consumers | Purpose |
|---|---|---|---|
InventoryReceivedEvent |
Stock receipt | WMS, Analytics | Update positions |
InventoryReservedEvent |
Order reservation | Order Management | Confirm availability |
InventoryAllocatedEvent |
Order allocation | WMS, Shipping | Trigger picking |
InventoryReleasedEvent |
Reservation expiry/cancel | Order Management | Update availability |
InventoryAdjustedEvent |
Manual adjustment | Finance, Analytics | Audit trail |
InventoryTransferredEvent |
Location transfer | WMS | Update locations |
LowStockAlertEvent |
Below reorder point | Procurement | Trigger replenishment |
StockoutEvent |
Zero availability | Sales, Marketing | Stop selling |
InventoryAgedEvent |
Aging threshold | Finance | Obsolescence tracking |
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
L1: Inventory Management
βββ L2: Stock Position Management
β βββ L3: Real-time Position Tracking
β βββ L3: Multi-warehouse Inventory
β βββ L3: Location-level Tracking
β βββ L3: Lot/Serial Tracking
β βββ L3: Inventory Aging
βββ L2: Reservation & Allocation
β βββ L3: Order Reservation
β βββ L3: Channel Allocation
β βββ L3: Reservation Expiration
β βββ L3: Backorder Management
β βββ L3: Priority Allocation
βββ L2: Stock Movements
β βββ L3: Receipts Processing
β βββ L3: Picking & Allocation
β βββ L3: Transfers & Relocations
β βββ L3: Returns Processing
β βββ L3: Adjustments & Write-offs
βββ L2: Inventory Control
β βββ L3: Cycle Counting
β βββ L3: Physical Inventory
β βββ L3: Variance Analysis
β βββ L3: Hold Management
β βββ L3: Quality Control
βββ L2: Analytics & Reporting
βββ L3: Inventory Valuation
βββ L3: Turn Analysis
βββ L3: ABC Classification
βββ L3: Aging Reports
βββ L3: Stock Coverage
Business Goal: Maintain 99.95% inventory accuracy with real-time visibility
Key Business Outcomes:
Strategic Value: CRITICAL - Foundation for all fulfillment operations
Purpose: Maintain accurate inventory positions across all states
Business Rules:
Technical Implementation:
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
@EventSourcedAggregate
public class InventoryItem {
@CommandHandler
public void handle(UpdateStockPositionCommand cmd) {
// Validate business rules
if (cmd.getQuantity().isNegative()) {
throw new NegativeInventoryException();
}
// Calculate new position
StockPosition newPosition = position.adjust(
cmd.getQuantity(),
cmd.getAdjustmentType()
);
// Apply event
apply(new StockPositionUpdatedEvent(
this.id,
this.sku,
this.location,
newPosition,
cmd.getReason()
));
}
@EventSourcingHandler
public void on(StockPositionUpdatedEvent event) {
this.position = event.getNewPosition();
this.lastModified = event.getTimestamp();
}
}
Metrics:
Purpose: Aggregate inventory across multiple facilities
Business Rules:
Query Model:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@QueryHandler
public class MultiWarehouseInventoryProjection {
public NetworkInventory getNetworkInventory(SKU sku) {
return NetworkInventory.builder()
.sku(sku)
.warehouses(
warehouseRepository.findAll().stream()
.map(wh -> getWarehouseInventory(sku, wh))
.collect(Collectors.toList())
)
.totalAvailable(calculateTotalATP())
.optimalFulfillmentLocation(findOptimalLocation())
.build();
}
}
Purpose: Track inventory at bin/shelf level for picking optimization
Capabilities:
Purpose: Guarantee inventory availability for customer orders
Business Rules:
Reservation State Machine:
1
2
3
PENDING β CONFIRMED β ALLOCATED β FULFILLED
β β β
EXPIRED CANCELLED CANCELLED
Implementation:
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
37
38
39
40
41
42
43
44
@Saga
public class OrderReservationSaga {
@StartSaga
@SagaEventHandler
public void handle(OrderCreatedEvent event) {
// Reserve each line item
event.getLineItems().forEach(item -> {
commandGateway.send(new ReserveInventoryCommand(
item.getSku(),
item.getQuantity(),
event.getOrderId(),
Duration.ofMinutes(15)
));
});
}
@SagaEventHandler
public void handle(InventoryReservedEvent event) {
reservedItems.add(event.getSku());
if (allItemsReserved()) {
commandGateway.send(new ConfirmOrderCommand(orderId));
endSaga();
}
}
@SagaEventHandler
public void handle(ReservationFailedEvent event) {
// Compensate by releasing successful reservations
reservedItems.forEach(sku -> {
commandGateway.send(new ReleaseInventoryCommand(
sku,
orderId
));
});
commandGateway.send(new CancelOrderCommand(
orderId,
"Insufficient inventory"
));
endSaga();
}
}
Purpose: Manage inventory allocation across sales channels
Business Rules:
Configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
allocation:
channels:
- name: website
priority: 1
reservation-pool: 60%
safety-stock: 100
- name: amazon
priority: 2
reservation-pool: 30%
safety-stock: 50
- name: wholesale
priority: 3
reservation-pool: 10%
safety-stock: 20
Purpose: Record incoming inventory from suppliers
Capabilities:
Event Flow:
1
ASNReceived β ReceiptStarted β ItemsReceived β QualityChecked β PutawayCompleted
Purpose: Move inventory between locations
Types:
Business Rules:
Purpose: Maintain accuracy through periodic counts
Strategy:
Workflow:
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
37
38
@Component
public class CycleCountWorkflow {
public CycleCountTask generateCountTask() {
// Select items based on ABC classification and last count date
List<SKU> itemsToCount = cycleCountStrategy.selectItems(
LocalDate.now(),
100 // items per task
);
return CycleCountTask.builder()
.taskId(UUID.randomUUID())
.items(itemsToCount)
.assignedTo(selectCounter())
.dueDate(LocalDate.now())
.blind(true) // Don't show expected quantity
.build();
}
public void processCountResults(
CycleCountTask task,
List<CountResult> results
) {
results.forEach(result -> {
Variance variance = calculateVariance(
result.getSku(),
result.getLocation(),
result.getCountedQuantity()
);
if (variance.isSignificant()) {
initiateRecountWorkflow(result);
} else {
applyAdjustment(result, variance);
}
});
}
}
Purpose: Calculate inventory value for financial reporting
Methods Supported:
Calculation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class InventoryValuationService {
public InventoryValuation calculateValuation(
ValuationMethod method,
LocalDate asOfDate
) {
return switch(method) {
case FIFO -> calculateFIFO(asOfDate);
case LIFO -> calculateLIFO(asOfDate);
case WEIGHTED_AVERAGE -> calculateWeightedAverage(asOfDate);
case SPECIFIC_ID -> calculateSpecificId(asOfDate);
};
}
private InventoryValuation calculateFIFO(LocalDate date) {
// Group receipts by date
// Apply earliest costs first
// Calculate total value
}
}
Purpose: Measure inventory efficiency
Metrics:
graph LR
PC[Product Catalog] -->|Product Master Data| IS[Inventory Service]
WMS[Warehouse Operations] -->|Physical Movements| IS
PROC[Procurement] -->|Purchase Orders| IS
style PC fill:#f9f,stroke:#333,stroke-width:2px
style WMS fill:#f9f,stroke:#333,stroke-width:2px
style PROC fill:#f9f,stroke:#333,stroke-width:2px
style IS fill:#bbf,stroke:#333,stroke-width:4px
Relationship Type: CONFORMIST
Integration Details:
Relationship Type: PARTNERSHIP
Integration Details:
Relationship Type: CUSTOMER-SUPPLIER
Published Interface:
1
2
3
4
GET /api/v1/inventory/availability/{sku}
POST /api/v1/inventory/reservations
DELETE /api/v1/inventory/reservations/{reservationId}
POST /api/v1/inventory/allocations
Relationship Type: OPEN HOST SERVICE
Published Events:
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
37
38
39
40
41
42
43
44
45
46
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Command Side β
β ββββββββββββββββ ββββββββββββββββ β
β β REST API β β Kafka β β
β β Commands β β Commands β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββββββββββββββββββββββββ β
β β Command Handlers β β
β β ββββββββββββββββββββββββββββ β β
β β β Aggregate Roots β β β
β β β - InventoryItem β β β
β β β - InventoryMovement β β β
β β ββββββββββββ¬ββββββββββββββββ β β
β β β β β
β β βΌ β β
β β ββββββββββββββββββββββββββββ β β
β β β Event Store β β β
β β β (PostgreSQL + Kafka) β β β
β β ββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββ β
β β
β Query Side β
β ββββββββββββββββββββββββββββββββββββββ β
β β Event Processors β β
β β ββββββββββββββββββββββββββββ β β
β β β Projections β β β
β β β - Inventory Positions β β β
β β β - Available to Promise β β β
β β β - Movement History β β β
β β ββββββββββββ¬ββββββββββββββββ β β
β β β β β
β β βΌ β β
β β ββββββββββββββββββββββββββββ β β
β β β Read Models β β β
β β β (PostgreSQL + Redis) β β β
β β ββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββ β
β β² β² β
β β β β
β ββββββββ΄ββββββββ ββββββββ΄ββββββββ β
β β REST API β β GraphQL β β
β β Queries β β Queries β β
β ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Language | Java | 21 | Core programming language |
| Framework | Spring Boot | 3.2.0 | Application framework |
| Event Store | PostgreSQL + Kafka | 15.0 / 3.5 | Event sourcing storage |
| Read Store | PostgreSQL | 15.0 | Materialized views |
| Cache | Redis | 7.2 | High-speed queries |
| Messaging | Apache Kafka | 3.5 | Event streaming |
| CQRS Framework | Axon Framework | 4.9 | CQRS/ES implementation |
| API Gateway | Spring Cloud Gateway | 4.1.0 | API routing |
| Monitoring | Micrometer + Prometheus | Latest | Metrics collection |
| Tracing | OpenTelemetry | 1.34 | Distributed tracing |
| Metric | Target | Actual | Status |
|---|---|---|---|
| ATP Query Response | <100ms | 45ms | β |
| Reservation Processing | <200ms | 125ms | β |
| Event Processing Rate | >1000/sec | 1500/sec | β |
| Inventory Accuracy | >99.95% | 99.97% | β |
| System Availability | 99.99% | 99.995% | β |
| Concurrent Users | 10000 | 15000 | β |
| KPI | Description | Target | Current | Impact |
|---|---|---|---|---|
| Inventory Accuracy | % of correct stock positions | 99.95% | 99.97% | Customer trust |
| Stockout Rate | % of orders with stockouts | <0.5% | 0.3% | $5M revenue protected |
| Inventory Turns | Annual inventory turnover | 12x | 13.5x | Working capital |
| Obsolete Inventory | % of obsolete stock | <2% | 1.5% | $1M waste reduction |
| Cycle Count Efficiency | Items counted per hour | 150 | 175 | Labor optimization |
| ATP Response Time | Query response latency | <100ms | 45ms | Customer experience |
Implementation Costs:
Annual Benefits:
| Risk | Probability | Impact | Mitigation Strategy |
|---|---|---|---|
| Event Store Failure | Low | Critical | Multi-region replication, snapshot recovery |
| Data Inconsistency | Medium | High | Event sourcing, compensating transactions |
| Performance Degradation | Medium | High | CQRS, caching, horizontal scaling |
| Integration Failure | Medium | Medium | Circuit breakers, fallback strategies |
| Concurrent Update Conflicts | High | Medium | Optimistic locking, retry logic |
| Risk | Probability | Impact | Mitigation Strategy |
|---|---|---|---|
| Inventory Shrinkage | Medium | High | Cycle counting, security measures |
| Obsolescence | Medium | Medium | Aging alerts, markdown strategies |
| Phantom Inventory | Low | High | Regular reconciliation, audit trails |
| Channel Conflicts | Medium | Medium | Clear allocation rules, monitoring |
Current Security Measures:
Compliance Requirements:
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
/api/v1/inventory/availability:
get:
summary: Get available to promise inventory
parameters:
- name: sku
required: true
- name: warehouse
required: false
responses:
200:
schema:
$ref: '#/components/schemas/AvailableToPromise'
/api/v1/inventory/reservations:
post:
summary: Create inventory reservation
requestBody:
schema:
$ref: '#/components/schemas/ReservationRequest'
responses:
201:
description: Reservation created
/api/v1/inventory/movements:
post:
summary: Record inventory movement
requestBody:
schema:
$ref: '#/components/schemas/MovementRequest'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// InventoryReservedEvent
{
"specversion": "1.0",
"type": "com.paklog.inventory.reserved.v1",
"source": "/inventory-service",
"id": "evt-123",
"time": "2025-01-20T10:00:00Z",
"datacontenttype": "application/json",
"data": {
"reservationId": "res-abc123",
"orderId": "order-456",
"sku": "SKU-789",
"quantity": 5,
"warehouse": "WH-001",
"expiresAt": "2025-01-20T10:15:00Z"
}
}
| Term | Definition |
|---|---|
| ATP | Available to Promise - inventory available for new orders |
| CQRS | Command Query Responsibility Segregation pattern |
| Event Sourcing | Storing state as sequence of events |
| SKU | Stock Keeping Unit - unique product identifier |
| FIFO | First In First Out inventory valuation |
| Safety Stock | Buffer inventory to prevent stockouts |
| Cycle Count | Periodic counting of inventory subset |
| Phantom Inventory | System inventory not physically present |
| Shrinkage | Loss of inventory due to theft, damage, errors |
| Obsolescence | Inventory that cannot be sold |
Document Version: 1.0.0 Last Updated: 2025-01-20 Status: APPROVED Next Review: 2025-04-20