Overview
Instance represents a specific instance/exemplar of a Product. While Product defines WHAT can be sold, Instance represents WHAT WAS actually sold/created.
Package: com.softwarearchetypes.inventory
Source: /inventory/src/main/java/com/softwarearchetypes/inventory/Instance.java:20
Interface
Common interface for:
- ProductInstance: Specific instance of a ProductType (e.g., iPhone with serial ABC123)
- PackageInstance: Specific instance of a PackageType (e.g., laptop bundle with customer’s choices)
Tracking Requirements
Every instance must be tracked by at least one of:
- SerialNumber: Individual tracking (unique identifier per item)
- Batch: Group tracking (multiple items tracked together)
Core Methods
Returns the unique instance identifier.
productId
ProductIdentifier productId()
Returns the product identifier this instance belongs to.
serialNumber
Optional<SerialNumber> serialNumber()
Returns the serial number if this instance has individual tracking.
The serial number, or empty if tracked by batch
batchId
Optional<BatchId> batchId()
Returns the batch ID if this instance is tracked as part of a batch.
The batch ID, or empty if individually tracked
quantity
Optional<Quantity> quantity()
Returns the quantity for this instance (relevant for batch-tracked items).
The quantity, or empty if not applicable
effectiveQuantity
default Quantity effectiveQuantity()
Returns the effective quantity - either the explicit quantity or 1 piece as default. Used for counting/summing instances.
The effective quantity (never null)
Tracking Strategies
Individual Tracking (Serial Number)
Used when each item needs unique identification:
Examples:
- Electronics with IMEI (phones)
- Vehicles with VIN
- High-value items requiring warranty tracking
Instance phone = ProductInstance.builder()
.id(InstanceId.random())
.productId(phoneProductId)
.serialNumber(new ImeiSerialNumber("123456789012345"))
.build();
assert phone.serialNumber().isPresent();
assert phone.batchId().isEmpty();
assert phone.effectiveQuantity().equals(Quantity.of(1, Unit.pieces()));
Batch Tracking
Used when individual identity is unimportant but origin/quality control matters:
Examples:
- Food products with expiry dates
- Manufactured parts from same production run
- Chemicals and pharmaceuticals
Instance flour = ProductInstance.builder()
.id(InstanceId.random())
.productId(flourProductId)
.batchId(BatchId.of("BATCH-2024-001"))
.quantity(Quantity.of(25, Unit.kilograms()))
.build();
assert flour.batchId().isPresent();
assert flour.serialNumber().isEmpty();
assert flour.quantity().isPresent();
assert flour.effectiveQuantity().equals(Quantity.of(25, Unit.kilograms()));
InstanceBuilder
The InstanceBuilder class provides a fluent API for creating instances.
Source: /inventory/src/main/java/com/softwarearchetypes/inventory/InstanceBuilder.java
Example Usage
// Individually tracked instance
Instance laptop = InstanceBuilder.forProduct(laptopProductId)
.withSerialNumber(new TextualSerialNumber("LAP-2024-001"))
.build();
// Batch tracked instance
Instance milk = InstanceBuilder.forProduct(milkProductId)
.withBatch(batchId)
.withQuantity(Quantity.of(10, Unit.liters()))
.build();
ProductInstance
Concrete implementation for product instances.
Source: /inventory/src/main/java/com/softwarearchetypes/inventory/ProductInstance.java
class ProductInstance implements Instance
Constructor
private ProductInstance(InstanceId id,
ProductIdentifier productId,
SerialNumber serialNumber,
BatchId batchId,
Quantity quantity)
productId
ProductIdentifier
required
Product identifier
Serial number (required if no batch)
Batch ID (required if no serial number)
Quantity (typically used with batch)
builder
Creates a builder for constructing ProductInstance.
Usage Examples
Electronics Store
// Phone with IMEI
Instance iPhone = ProductInstance.builder()
.id(InstanceId.random())
.productId(iPhoneProductId)
.serialNumber(new ImeiSerialNumber("353456789012345"))
.build();
// Laptop with VIN-like serial
Instance laptop = ProductInstance.builder()
.id(InstanceId.random())
.productId(laptopProductId)
.serialNumber(new TextualSerialNumber("LAP-2024-XYZ-001"))
.build();
Grocery Store
// Fresh milk from batch
BatchId milkBatch = BatchId.of("MILK-2024-03-08-A");
Instance milk = ProductInstance.builder()
.id(InstanceId.random())
.productId(milkProductId)
.batchId(milkBatch)
.quantity(Quantity.of(50, Unit.liters()))
.build();
// Flour from batch
BatchId flourBatch = BatchId.of("FLOUR-2024-Q1-001");
Instance flour = ProductInstance.builder()
.id(InstanceId.random())
.productId(flourProductId)
.batchId(flourBatch)
.quantity(Quantity.of(1000, Unit.kilograms()))
.build();
Automotive
// Car with VIN
Instance car = ProductInstance.builder()
.id(InstanceId.random())
.productId(carProductId)
.serialNumber(new VinSerialNumber("1HGBH41JXMN109186"))
.build();
assert car.serialNumber().isPresent();
VinSerialNumber vin = (VinSerialNumber) car.serialNumber().get();
Pharmaceutical
// Medicine from batch with expiry tracking
BatchId medicineBatch = BatchId.of("MED-2024-001");
Instance medicine = ProductInstance.builder()
.id(InstanceId.random())
.productId(medicineProductId)
.batchId(medicineBatch)
.quantity(Quantity.of(500, Unit.pieces()))
.build();
// Check effective quantity for counting
Quantity total = medicine.effectiveQuantity(); // 500 pieces
Querying Instances
Instances can be queried using the InstanceRepository:
InstanceRepository repository = // ... injected
// Find by serial number
Optional<Instance> found = repository.findBySerialNumber(
new ImeiSerialNumber("353456789012345")
);
// Find by batch
List<Instance> batchInstances = repository.findByBatch(milkBatch);
// Find by product
List<Instance> productInstances = repository.findByProduct(iPhoneProductId);
Instance vs Product
Product (ProductType):
- Definition of what can be sold
- Template/blueprint
- Examples: “iPhone 15 Pro”, “Organic Whole Milk 1L”
Instance:
- Specific item that was/will be sold
- Actual physical or digital item
- Examples: “iPhone with IMEI 353456789012345”, “Milk from batch MILK-2024-03-08-A”
// Product: what we sell
ProductType iPhoneProduct = ProductType.builder()
.id(ProductIdentifier.random())
.name(ProductName.of("iPhone 15 Pro"))
.trackingStrategy(ProductTrackingStrategy.SERIAL_NUMBER)
.build();
// Instances: specific items sold
Instance phone1 = ProductInstance.builder()
.productId(iPhoneProduct.id())
.serialNumber(new ImeiSerialNumber("353456789012345"))
.build();
Instance phone2 = ProductInstance.builder()
.productId(iPhoneProduct.id())
.serialNumber(new ImeiSerialNumber("353456789012346"))
.build();
// Both instances are of the same product type
assert phone1.productId().equals(phone2.productId());
// But they are different instances
assert !phone1.id().equals(phone2.id());