Prerequisites
Core Concepts
Tracking Strategies
- Individually Tracked - Each item is unique (e.g., laptops with serial numbers)
- Batch Tracked - Items grouped by production batch (e.g., pharmaceuticals)
- Identical - Fungible items without individual identity (e.g., gasoline)
Key Entities
- Inventory Entry - Represents a product in the catalog
- Instance - A specific occurrence of a product (e.g., one laptop with SN-12345)
- Resource - Availability-tracked entity that can be locked/reserved
Step-by-Step Tutorial
import com.softwarearchetypes.inventory.*;
import com.softwarearchetypes.inventory.availability.*;
import java.time.*;
Clock clock = Clock.systemDefaultZone();
AvailabilityConfiguration availabilityConfig =
AvailabilityConfiguration.inMemory(clock);
InventoryConfiguration config =
InventoryConfiguration.inMemory(availabilityConfig);
InventoryFacade facade = config.facade();
import com.softwarearchetypes.common.Result;
ProductIdentifier laptopProductId = ProductIdentifier.random();
InventoryProduct laptop = InventoryProduct.individuallyTracked(
laptopProductId,
"MacBook Pro 16"
);
Result<String, InventoryEntryId> result = facade.handle(
CreateInventoryEntry.forProduct(laptop)
);
if (result.success()) {
InventoryEntryId entryId = result.getSuccess();
System.out.println("Inventory entry created: " + entryId);
// Verify creation
Optional<InventoryEntryView> view = facade.findEntry(entryId);
view.ifPresent(entry -> {
System.out.println("Product: " + entry.productName());
System.out.println("Tracking: individually tracked");
System.out.println("Instances: " + entry.instanceIds().size());
});
}
// Add first laptop
CreateInstance command1 = CreateInstance.forProduct(laptopProductId)
.withSerial("SN-LAPTOP-001")
.withFeatures(Map.of(
"color", "space-gray",
"ram", "32GB",
"storage", "1TB"
))
.build();
Result<String, InstanceId> instance1Result = facade.createInstance(command1);
InstanceId instance1 = instance1Result.getSuccess();
// Add second laptop
CreateInstance command2 = CreateInstance.forProduct(laptopProductId)
.withSerial("SN-LAPTOP-002")
.withFeatures(Map.of(
"color", "silver",
"ram", "16GB",
"storage", "512GB"
))
.build();
InstanceId instance2 = facade.createInstance(command2).getSuccess();
System.out.println("Added 2 laptop instances");
import com.softwarearchetypes.quantity.*;
ProductIdentifier fuelProductId = ProductIdentifier.random();
InventoryProduct fuel = InventoryProduct.batchTracked(
fuelProductId,
"Gasoline 95"
);
InventoryEntryId fuelEntryId = facade.handle(
CreateInventoryEntry.forProduct(fuel)
).getSuccess();
// Add batch deliveries
BatchId batch1 = BatchId.random();
CreateInstance delivery1 = CreateInstance.forProduct(fuelProductId)
.withBatch(batch1)
.withQuantity(Quantity.of(5000, Unit.liters()))
.build();
facade.createInstance(delivery1);
BatchId batch2 = BatchId.random();
CreateInstance delivery2 = CreateInstance.forProduct(fuelProductId)
.withBatch(batch2)
.withQuantity(Quantity.of(3000, Unit.liters()))
.build();
facade.createInstance(delivery2);
System.out.println("Added 2 fuel batches");
System.out.println("Total fuel: " + facade.countProduct(fuelProductId));
ProductIdentifier milkProductId = ProductIdentifier.random();
InventoryProduct milk = InventoryProduct.identical(
milkProductId,
"Whole Milk 1L"
);
InventoryEntryId milkEntryId = facade.handle(
CreateInventoryEntry.forProduct(milk)
).getSuccess();
// Add milk inventory
CreateInstance milkDelivery = CreateInstance.forProduct(milkProductId)
.withQuantity(Quantity.of(100, Unit.pieces()))
.build();
facade.createInstance(milkDelivery);
System.out.println("Milk inventory: " +
facade.countProduct(milkProductId));
// Find instance by serial number
Optional<InstanceView> found = facade.findInstanceBySerial(
SerialNumber.of("SN-LAPTOP-001")
);
found.ifPresent(instance -> {
System.out.println("Found laptop: " + instance.serialNumber());
System.out.println("Features: " + instance.features());
});
// Find all instances of a product
List<InstanceView> allLaptops = facade.findInstancesByProduct(laptopProductId);
System.out.println("Total laptops: " + allLaptops.size());
// Find instances by batch
List<InstanceView> batch1Fuel = facade.findInstancesByBatch(batch1);
System.out.println("Batch 1 fuel deliveries: " + batch1Fuel.size());
// Count all instances of a product
Quantity totalLaptops = facade.countProduct(laptopProductId);
System.out.println("Total laptops: " + totalLaptops.amount() + " " +
totalLaptops.unit());
// Count specific batch
Quantity batch1Quantity = facade.countProduct(
fuelProductId,
InstanceCriteria.byBatch(batch1)
);
System.out.println("Batch 1 fuel: " + batch1Quantity);
// Count with feature criteria
Set<InstanceId> silverLaptops = facade.findInstances(
laptopProductId,
InstanceCriteria.byFeature("color", "silver")
);
System.out.println("Silver laptops: " + silverLaptops.size());
import com.softwarearchetypes.inventory.availability.*;
// Create availability resource for laptop 1
ResourceId resource1 = ResourceId.random();
AvailabilityFacade availabilityFacade = availabilityConfig.facade();
availabilityFacade.registerIndividualResource(resource1);
// Map instance to resource
facade.mapInstanceToResource(
entryId,
instance1,
resource1
);
System.out.println("Instance mapped to availability resource");
// Verify mapping
Optional<InventoryEntryView> entry = facade.findEntry(entryId);
entry.ifPresent(e -> {
Map<InstanceId, ResourceId> mappings = e.instanceToResource();
System.out.println("Mapped instances: " + mappings.size());
});
OwnerId customerId = OwnerId.random();
// Lock the laptop for customer
LockCommand lockCmd = new LockCommand(
laptopProductId,
Quantity.of(1, Unit.pieces()),
customerId,
ResourceSpecification.IndividualSpecification.of(instance1)
);
Result<String, List<BlockadeId>> lockResult = facade.handle(lockCmd);
if (lockResult.success()) {
List<BlockadeId> blockades = lockResult.getSuccess();
System.out.println("Laptop reserved for customer");
System.out.println("Blockade IDs: " + blockades);
} else {
System.err.println("Reservation failed: " + lockResult.getFailure());
}
Result<String, InventoryEntryId> removeResult =
facade.removeInstanceFromEntry(entryId, instance2);
if (removeResult.success()) {
System.out.println("Instance removed from inventory");
// Count remaining
Quantity remaining = facade.countProduct(laptopProductId);
System.out.println("Remaining laptops: " + remaining.amount());
}
Advanced: Temporal Resource Locking
For time-slotted resources like hotel rooms:Complete Example
Common Patterns
Pattern: Find Available Instances
Pattern: Bulk Instance Creation
Pattern: Low Stock Alert
Next Steps
- Learn about Availability Strategies for reservation logic
- Explore Resource Pooling for fungible resources
- See Inventory Reporting for analytics
The Inventory module separates what you have (inventory) from what you can promise (availability). This enables sophisticated reservation and allocation strategies.
