supply_inventory 1.0.0
supply_inventory: ^1.0.0 copied to clipboard
A production-grade DDD package for supply chain and inventory management with strict invariant protection and time safety.
supply_inventory #
A production-grade Domain-Driven Design (DDD) package for supply chain and inventory management in Dart.
Philosophy #
"This domain controls availability. If it lies, the business lies."
Safety over convenience. The domain throws early and refuses to accept invalid states. Every invariant is protected, every state change is tracked, and time is injected to ensure deterministic, testable behavior.
Features #
- ✅ Strict DDD Architecture - Bounded context with clear domain boundaries
- ✅ Domain Purity - No framework dependencies, no
DateTime.now()in domain - ✅ Time Safety - Clock abstraction for deterministic, replayable behavior
- ✅ Invariant Protection - Stock never goes negative, lifecycle strictly enforced
- ✅ Event-Driven - All state changes emit domain events for audit trails
- ✅ Type Safety - Immutable value objects with validation
- ✅ Test-First - Comprehensive tests with FakeClock for reproducibility
- ✅ DIP Compliance - Domain defines interfaces, infrastructure implements
Non-Negotiable Rules #
1. Stock Never Goes Negative #
final item = StockItem(
productId: ProductId('PROD-1'),
initialQuantity: Quantity(10),
// ...
);
item.deplete(Quantity(11), clock); // ❌ Throws ArgumentError
2. No Silent Corrections #
order.receive({
ProductId('PROD-1'): Quantity(90), // Expected 100
}, clock); // ❌ Throws ArgumentError - quantity mismatch
3. Time is Injected #
// ✅ Production
final inventory = Inventory(
id: 'INV-1',
clock: SystemClock(), // Real time
);
// ✅ Testing
final clock = FakeClock(DateTime(2026, 1, 1));
final inventory = Inventory(id: 'INV-1', clock: clock);
4. Lifecycle is Immutable #
final order = SupplyOrder(/* ... */);
order.approve(clock); // pending -> approved ✅
order.ship(clock); // approved -> shipped ✅
order.cancel(clock); // ❌ Throws StateError - must be pending
Installation #
Add to your pubspec.yaml:
dependencies:
supply_inventory: ^1.0.0
Usage #
Basic Inventory Management #
import 'package:supply_inventory/supply_inventory.dart';
void main() {
// Create inventory with time injection
final inventory = Inventory(
id: 'WAREHOUSE-1',
clock: SystemClock(),
);
// Add a product
inventory.addProduct(
productId: ProductId('RICE-001'),
initialQuantity: Quantity(1000),
unit: Unit.kilograms,
reorderPoint: Quantity(200),
productType: ProductType.raw,
);
// Deplete stock (domain validates it never goes negative)
inventory.depleteStock(ProductId('RICE-001'), Quantity(150));
// Check current stock
final stock = inventory.getStock(ProductId('RICE-001'));
print('Current stock: ${stock?.value} kg'); // 850 kg
// Collect events for auditing
final events = inventory.collectDomainEvents();
for (final event in events) {
print(event); // StockDepleted, StockReplenished, etc.
}
}
Supply Order Workflow #
// Create a supply order
final order = SupplyOrder(
id: 'SO-12345',
supplierId: SupplierId('SUPPLIER-A'),
items: {
ProductId('RICE-001'): Quantity(500),
ProductId('WHEAT-002'): Quantity(300),
},
clock: SystemClock(),
);
// Follow strict lifecycle
order.approve(clock); // pending -> approved
order.ship(clock); // approved -> shipped
// Receive and verify (throws if quantities don't match)
order.receive({
ProductId('RICE-001'): Quantity(500),
ProductId('WHEAT-002'): Quantity(300),
}, clock); // ✅ Exact match required
Restock Policy #
final policy = RestockPolicy();
final productsToRestock = policy.evaluate(inventory);
for (final productId in productsToRestock) {
print('$productId needs restocking');
}
Full Integration with Service #
final service = InventoryManagementService(
inventory: inventory,
restockPolicy: RestockPolicy(),
clock: SystemClock(),
supplier: mySupplierImplementation, // Optional
);
// Check and automatically create orders for low stock
final orderIds = await service.checkAndReorder(
supplierId: SupplierId('SUPPLIER-A'),
restockQuantities: {
ProductId('RICE-001'): Quantity(1000),
},
);
// Process the workflow
service.approveOrder(orderIds.first);
service.shipOrder(orderIds.first);
service.processSupplyOrderReceipt(orderIds.first, receivedItems);
Architecture #
lib/
src/
domain/ # Pure domain logic
entities/ # Inventory, StockItem, SupplyOrder
value_objects/ # ProductId, Quantity, SupplierId, Unit
enums/ # ProductType, SupplyOrderStatus
events/ # Domain events for state changes
policies/ # RestockPolicy
ports/ # Abstract interfaces (Clock, Supplier)
infrastructure/ # Concrete implementations (SystemClock)
services/ # Application orchestration
Testing #
All tests use FakeClock for deterministic behavior:
test('stock depletion emits event', () {
final clock = FakeClock(DateTime(2026, 1, 1));
final item = StockItem(/* ... */);
item.deplete(Quantity(50), clock);
final event = item.domainEvents.first as StockDepleted;
expect(event.occurredAt, equals(DateTime(2026, 1, 1))); // Deterministic!
});
Run tests:
dart test
Principles Applied #
- Bounded Context: Supply & Inventory only - no ordering/delivery/customer logic
- Aggregate Roots: Inventory controls all stock operations
- Value Objects: Immutable ProductId, Quantity, etc.
- Domain Events: Audit trail for all state changes
- Dependency Inversion: Domain defines Supplier interface
- Tell, Don't Ask: Entities protect their own invariants
License #
MIT
Contributing #
This package follows strict DDD principles. All contributions must:
- Maintain domain purity (no DateTime.now() in domain)
- Protect all invariants with tests
- Use Clock injection for time
- Include comprehensive tests
- Never bypass entity rules in services