Bill of Material Aggregate
Purpose
The BillOfMaterial aggregate represents the formal definition of component relationships in manufacturing operations, specifying exactly which items and in what quantities are required to produce a unit of a parent item. It serves as the cornerstone of material planning, production scheduling, component explosion, and assembly transaction validation.
This aggregate is fundamentally responsible for maintaining the structural integrity of product definitions, ensuring that all assembly operations have accurate component requirements, and providing the calculation engine for material requirements planning. The BillOfMaterial aggregate acts as the authoritative source for "what goes into making this product" across the entire manufacturing lifecycle.
Business Rules & Invariants
Core Business Rules
-
Single Parent Item: Each BOM is associated with exactly one parent item that is produced by the assembly process. The parent item represents the output of the manufacturing operation.
-
Produced Unit Specification: Each BOM must specify which UnitOfMeasure is produced. This allows different BOMs for the same item producing different units (e.g., one BOM for "1 case" and another for "1 pallet").
-
No Circular References: A BOM's parent item cannot be one of its own components, either directly or indirectly (in multi-level BOMs). This prevents infinite recursion and logically impossible manufacturing processes.
-
Component Uniqueness: Each component item can appear at most once in a BOM. If the same component is needed multiple times, the quantity should be aggregated into a single line.
-
Positive Quantities: All component quantities must be positive (greater than zero). Zero or negative quantities are logically invalid for component requirements.
-
Valid Component Items: All component items must exist, be active, and typically should be stockable items since they are consumed from inventory.
-
Immutable Lines: BillOfMaterialLine entities are immutable after creation. Updates require removal and recreation of lines to maintain audit trail integrity.
Domain Invariants
- Parent Item Required: ParentItemId cannot be null or empty
- Produced Unit Required: ProducedUnitOfMeasureId cannot be null or empty
- Name Required: BOM name cannot be null or empty
- Component Validation: Each line must have valid ComponentItemId, Quantity > 0, and UnitOfMeasureId
- Active Status: Soft delete via IsActive flag (inherited from AggregateRoot)
- Line Ownership: BillOfMaterialLine entities exist only within their parent BOM aggregate
State Management
BOM Lifecycle States
[Creation] → [Component Definition] → [Active Use] → [Modification] → [Archive]
↓
[Assembly Operations]
↓
[Component Explosion]
Component Management States
[Empty BOM] → [Add Components] → [Complete BOM] → [Modify Components]
↓
[Explosion/Validation]
↓
[Assembly Processing]
Update Patterns
[Existing BOM] → [SyncComponents] → [Updated BOM]
↓
[Add New / Update Existing / Remove Old]
Aggregate Components
1. BillOfMaterial (Aggregate Root)
Properties:
Parent Item Reference:
ParentItemId: Foreign key to the item produced by this BOMParentItem: Navigation property to the parent Item entity
Production Specification:
ProducedUnitOfMeasureId: Foreign key to the unit of measure producedProducedUnitOfMeasure: Navigation property to the UnitOfMeasure entity
Identification:
Name: Descriptive name for the BOM (e.g., "Standard Widget Assembly")Description: Optional detailed description of assembly process
Component Collection:
Lines: Read-only collection of BillOfMaterialLine entities representing components
Base Properties (from AggregateRoot):
Id: System-generated unique identifierCreatedDate: Timestamp of BOM creationModifiedDate: Timestamp of last modificationIsActive: Soft delete flag
Key Behaviors:
Factory Methods:
Create(): Static factory method creating new BOM with validation
Component Management:
AddComponent(): Adds a single component with validationSyncComponents(): Synchronizes entire component list (add/update/remove)UpdateHeader(): Modifies BOM header information
Calculation Methods:
Explode(): Calculates total component requirements for production quantity
Validation Logic:
The BillOfMaterial aggregate enforces validation at multiple levels:
- Construction Validation: Factory method validates parent item and produced unit
- Component Validation: AddComponent validates circular references and duplicates
- Quantity Validation: Ensures positive quantities for components
- Business Rule Enforcement: Prevents invalid BOM configurations
Central Manufacturing Role:
The BillOfMaterial serves as the authoritative definition for:
- Material Planning: Determines component requirements for production
- Assembly Transactions: Validates and guides assembly operations
- Inventory Consumption: Specifies which items to consume and in what quantities
- Production Costing: Foundation for standard cost calculations
- Component Availability: Enables feasibility checks before production
2. BillOfMaterialLine (Entity)
Purpose: Represents a single component requirement within a BOM, specifying which component item, how much of it, and in what unit of measure is required.
Properties:
Id: Unique identifier for the lineBillOfMaterialId: Foreign key to parent BOMComponentItemId: Foreign key to component itemComponentItem: Navigation property to component Item entityQuantity: Amount of component required per unit of parentUnitOfMeasureId: Unit of measure for the component quantityUnitOfMeasure: Navigation property to UnitOfMeasure entity
Key Characteristics:
- Immutability: Lines are immutable after creation for audit trail integrity
- Owned Entity: Lines cannot exist independently of their parent BOM
- Simple Structure: Contains only essential component specification data
- Factory Pattern: Created via static Create() method
Factory Method:
public static BillOfMaterialLine Create(
Guid billOfMaterialId,
Guid componentItemId,
decimal quantity,
Guid unitOfMeasureId)
Business Significance:
BOM lines provide the granular component details that drive:
- Component Explosion: Each line multiplies its quantity by production quantity
- Inventory Validation: Each line's component checked for availability
- Assembly Processing: Each line consumed from inventory during assembly
- Cost Roll-Up: Each line's component cost contributes to product cost
Immutability Pattern:
Lines are immutable to:
- Maintain audit trail integrity
- Simplify concurrency handling
- Ensure historical accuracy
- Support event sourcing patterns
When updates are needed:
- Remove old line from collection
- Create new line with updated values
- Add new line to collection
- Unit of Work handles persistence atomically
3. RequiredComponent (Value Object)
Purpose: Represents the calculated component requirement after explosion, used as the output of the Explode() method.
Properties:
ComponentItemId: Which component item is requiredQuantity: Total quantity required (base quantity × production quantity)UnitOfMeasureId: Unit of measure for the requirement
Characteristics:
- Immutable: Value object cannot be modified after creation
- Query Result: Used only as output from explosion calculations
- No Identity: Identified by its properties, not by an ID
- Temporary: Exists only during explosion process, not persisted
Usage Context:
// Explosion returns collection of RequiredComponents
IReadOnlyCollection<RequiredComponent> requirements = bom.Explode(100);
// Each RequiredComponent represents a calculated need
foreach (var req in requirements)
{
// Check inventory for this requirement
var available = await inventory.GetAvailableAsync(req.ComponentItemId);
if (available < req.Quantity)
throw new InsufficientInventoryException();
}
Domain Events (Future Enhancement)
BOM Lifecycle Events
-
BillOfMaterialCreatedDomainEvent: Raised when new BOM is created
- Contains BOMId, ParentItemId for integration tracking
- Enables cache invalidation and index updates
- Supports audit trail maintenance
-
BillOfMaterialUpdatedDomainEvent: Raised when BOM header is modified
- Contains old and new values for comparison
- Triggers revalidation of dependent assembly transactions
- Supports workflow automation
-
BillOfMaterialArchivedDomainEvent: Raised when BOM is archived
- Triggers impact analysis for active production orders
- Enables cleanup of dependent processes
- Supports compliance requirements
Component Management Events
-
ComponentAddedDomainEvent: Raised when component is added to BOM
- Contains ComponentItemId and quantity
- Enables dependent system updates
- Supports real-time cost recalculation
-
ComponentRemovedDomainEvent: Raised when component is removed
- Triggers revalidation of assembly feasibility
- Enables inventory planning updates
- Supports audit compliance
-
ComponentsSynchronizedDomainEvent: Raised after SyncComponents completes
- Contains before/after component list
- Enables comprehensive impact analysis
- Supports batch processing scenarios
Repository Contract
IBillOfMaterialRepository
Core Query Methods:
GetByIdAsync(Guid id): Retrieves single BOM with all lines and related itemsGetAllAsync(): Retrieves all active BOMsGetByParentItemAsync(Guid parentItemId): Finds BOM for specific parent itemGetArchivedAsync(): Retrieves archived (inactive) BOMs
Component Query Methods:
GetByComponentItemAsync(Guid componentItemId): Finds all BOMs using specific componentGetBOMsWithComponentsAsync(): Loads BOMs with full component item detailsGetComponentUsageCountAsync(Guid componentItemId): Counts how many BOMs use component
Validation Methods:
ExistsByParentItemAsync(Guid parentItemId): Checks if parent item has BOM definedHasBOMAsync(Guid parentItemId): Alias for existence checkIsComponentUsedAsync(Guid componentItemId): Verifies if item is used as component anywhereCanDeleteBOMAsync(Guid bomId): Validates BOM can be safely deleted
Parent Item Queries:
GetParentItemsWithBOMsAsync(): Gets list of items that have BOMs definedGetManufacturableItemsAsync(): Gets items that can be assembled (have BOMs)
Command Methods:
AddAsync(BillOfMaterial bom): Persists new BOM with all linesUpdate(BillOfMaterial bom): Updates existing BOM (handles line changes)Archive(BillOfMaterial bom): Soft delete (sets IsActive = false)UnArchive(BillOfMaterial bom): Restores archived BOM
Performance Optimization:
- Eager Loading: Include lines and related items in single query
- Explosion Support: Optimized queries for explosion operations
- Component Search: Efficient reverse lookup from component to BOMs
- Caching Friendly: Query patterns support caching strategies
Domain Services
IBOMService (Future Enhancement)
Purpose: Provides cross-aggregate coordination and complex BOM operations, particularly multi-level explosions and cost calculations.
Planned Methods:
Multi-Level Operations:
Task<IEnumerable<RequiredComponent>> ExplodeMultiLevelAsync(
Guid parentItemId,
decimal quantity,
int maxDepth = 10)
- Recursively explodes BOMs through entire product structure
- Handles sub-assemblies automatically
- Aggregates duplicate components across levels
- Prevents infinite recursion with max depth
Validation Services:
Task<ValidationResult> ValidateBOMIntegrityAsync(Guid bomId)
Task<bool> HasCircularReferenceAsync(Guid bomId)
Task<IEnumerable<ValidationError>> ValidateComponentAvailabilityAsync(
Guid bomId,
decimal quantity)
- Validates all components exist and are valid
- Checks for circular references in multi-level scenarios
- Verifies component availability for production
- Validates unit compatibility
Cost Services:
Task<decimal> CalculateStandardCostAsync(Guid parentItemId)
Task<CostBreakdown> GetCostBreakdownAsync(Guid bomId)
- Rolls up component costs to parent item
- Includes material overhead allocations
- Supports standard costing systems
- Provides detailed cost breakdowns
Planning Services:
Task<MaterialRequirementsPlan> GenerateMRPAsync(
IEnumerable<(Guid ItemId, decimal Quantity)> demands)
- Generates material requirements plan for multiple products
- Aggregates component needs across products
- Considers current inventory levels
- Supports production scheduling
Usage Patterns
Creating Complete BOM
// 1. Create BOM header
var bom = BillOfMaterial.Create(
parentItemId: finishedGoodsItemId,
producedUnitOfMeasureId: eachUnitId,
name: "Premium Widget Assembly - Standard Process",
description: "Primary assembly for premium widget line. " +
"Updated Q4 2024 for efficiency improvements.");
// 2. Add all components
bom.AddComponent(steelFrameId, 1m, eachUnitId);
bom.AddComponent(motorAssemblyId, 1m, eachUnitId);
bom.AddComponent(boltM10Id, 8m, eachUnitId);
bom.AddComponent(paintId, 0.5m, literUnitId);
bom.AddComponent(packagingId, 1m, eachUnitId);
// 3. Persist
await bomRepository.AddAsync(bom);
await unitOfWork.CommitAsync();
Explosion for Production Planning
// 1. Load BOM for item to be produced
var bom = await bomRepository.GetByParentItemAsync(productItemId);
// 2. Determine production quantity
decimal plannedProduction = 500m; // Plan to produce 500 units
// 3. Explode BOM
var requirements = bom.Explode(plannedProduction);
// 4. Generate material requirements report
var report = new MaterialRequirementsPlan();
foreach (var req in requirements)
{
var item = await itemRepository.GetByIdAsync(req.ComponentItemId);
var available = await inventoryService.GetAvailableAsync(req.ComponentItemId);
var shortage = Math.Max(0, req.Quantity - available);
report.AddLine(new MRPLine
{
ComponentItem = item.Name,
RequiredQuantity = req.Quantity,
AvailableQuantity = available,
ShortageQuantity = shortage,
UnitOfMeasure = req.UnitOfMeasureId
});
}
// 5. Present to planner for action
return report;
Updating BOM Components
// 1. Load existing BOM
var bom = await bomRepository.GetByIdAsync(bomId);
// Current state:
// - Steel Frame: 1 EA
// - Motor: 1 EA
// - Bolt M10: 8 EA
// - Paint: 0.5 L
// - Packaging: 1 EA
// 2. Define new desired state
var updatedComponents = new[]
{
(steelFrameId, 1m, eachUnitId), // Unchanged
(improvedMotorId, 1m, eachUnitId), // Changed to improved motor
(boltM12Id, 8m, eachUnitId), // Changed from M10 to M12 bolts
(paintId, 0.3m, literUnitId), // Reduced paint quantity
(packagingId, 1m, eachUnitId), // Unchanged
(warrantyCardId, 1m, eachUnitId) // New component added
};
// 3. Sync in single operation
bom.SyncComponents(updatedComponents);
// 4. Update header if needed
bom.UpdateHeader(
name: "Premium Widget Assembly - Improved Process",
description: "Updated Q1 2025: Improved motor, optimized paint usage, " +
"added warranty card. Uses M12 bolts for better durability.");
// 5. Save changes
await unitOfWork.CommitAsync();
// Result:
// - Old motor line removed, new improved motor added
// - Bolt type changed
// - Paint quantity updated
// - Warranty card added
// - All in single atomic transaction
Feasibility Check Before Assembly
// 1. Load BOM
var bom = await bomRepository.GetByParentItemAsync(parentItemId);
// 2. Desired assembly quantity
decimal assemblyQuantity = 50m;
// 3. Explode requirements
var required = bom.Explode(assemblyQuantity);
// 4. Check each component availability
var feasibilityReport = new AssemblyFeasibilityReport();
bool isFeasible = true;
foreach (var req in required)
{
var componentItem = await itemRepository.GetByIdAsync(req.ComponentItemId);
// Get available inventory at assembly location
var available = await inventoryService.GetAvailableQuantityAsync(
req.ComponentItemId,
assemblyLocationId,
req.UnitOfMeasureId);
var status = available >= req.Quantity
? ComponentStatus.Available
: ComponentStatus.Insufficient;
feasibilityReport.AddComponent(new ComponentAvailability
{
ComponentName = componentItem.Name,
RequiredQuantity = req.Quantity,
AvailableQuantity = available,
ShortageQuantity = Math.Max(0, req.Quantity - available),
Status = status
});
if (status == ComponentStatus.Insufficient)
isFeasible = false;
}
// 5. Return feasibility result
if (!isFeasible)
{
throw new InsufficientComponentsException(
$"Cannot assemble {assemblyQuantity} units. See report for details.",
feasibilityReport);
}
// Assembly can proceed
Performance Considerations
Optimization Strategies
- Eager Loading: Repository loads BOM with all lines and related items in single query
- Explosion Caching: Cache explosion results for repeated production of same item
- Component Indexing: Fast lookups for components and reverse lookups (component → BOMs)
- Read-Heavy Optimization: BOMs are read frequently, written infrequently
Scalability Metrics
- BOM Creation: <20ms including all component lines
- Single-Level Explosion: <5ms for BOM with 20 components
- Multi-Level Explosion: <50ms for 3-level structure with 50 total components (future)
- Component Search: <10ms to find all BOMs using specific component
- Memory Footprint: ~1KB per BOM header + ~200 bytes per line
Database Optimization
- Clustered Index: Primary key on BOM Id for entity framework
- Foreign Key Indexes: Indexed on ParentItemId for fast parent item lookups
- Line Indexes: Composite index on (BillOfMaterialId, ComponentItemId) for line queries
- Component Index: Index on ComponentItemId for reverse lookups
- Explosion Support: Optimized for frequent read operations during production
Integration with Other Aggregates
Item Aggregate
- Parent Item Relationship: BOM references parent item to be produced
- Component Relationships: Each line references component item
- Stockable Validation: Components typically must be stockable items
- Unit Class Compatibility: Component units must match item's unit class
Transaction Aggregate
- Assembly Validation: Assembly transactions require BOM for parent item
- Component Explosion: Transaction uses BOM to determine component consumption
- Quantity Validation: Transaction validates against exploded requirements
- Audit Trail: Transaction records which BOM was used
Inventory Aggregate
- Component Availability: BOM explosion drives inventory availability checks
- Consumption Tracking: Assembly updates inventory based on BOM components
- Location Management: Components can be sourced from multiple locations
- Backflush Support: Future automatic component consumption based on BOM
UnitOfMeasure Aggregate
- Produced Unit: BOM specifies which unit of parent item is produced
- Component Units: Each component line specifies its unit
- Explosion Calculations: Units preserved through explosion process
- Conversion Support: Future support for unit conversions in BOMs
Design Patterns Applied
Domain-Driven Design Patterns
- Aggregate Pattern: BOM maintains consistency boundary for component relationships
- Repository Pattern: Specialized data access with explosion support
- Factory Method Pattern: Static Create() enforces invariants
- Immutable Entities: BOM lines are immutable for audit integrity
- Value Object Pattern: RequiredComponent as explosion result
Behavioral Patterns
- Explosion Pattern: Component explosion is core behavior
- Validation Pattern: Multi-level validation prevents invalid BOMs
- Sync Pattern: SyncComponents handles complex update scenarios
- Calculation Pattern: Quantity explosion is pure calculation (no side effects)
Structural Patterns
- Composition Pattern: BOM composed of lines
- Owned Entity Pattern: Lines owned by BOM aggregate
- Collection Management: Encapsulated line collection with controlled access
Security and Compliance Considerations
Access Control
- BOM Creation: Restricted to authorized engineering/production roles
- Component Changes: Modifications require elevated privileges
- Archive Operations: BOM archival controlled through workflows
- Version Control: Future versioning supports change management processes
Audit and Compliance
- Complete Audit Trail: All BOM changes tracked via timestamps
- Immutable Lines: Line immutability ensures historical accuracy
- Change Tracking: ModifiedDate tracks last update
- Component History: Future events track all component changes
Data Integrity
- Referential Integrity: Foreign keys prevent orphaned relationships
- Circular Reference Prevention: Business rules prevent impossible structures
- Transaction Consistency: Unit of Work ensures atomic changes
- Validation Enforcement: Comprehensive validation prevents invalid states
Future Evolution
Planned Enhancements
- Multi-Level BOMs: Recursive explosion through sub-assemblies
- BOM Versioning: Full version control with effective dating
- Alternate BOMs: Multiple production methods for same item
- Co-Products/By-Products: Support for multiple outputs
- Scrap Factor: Built-in waste/scrap calculations
- Operation Routing: Link BOMs to production routings and work centers
- Component Substitutions: Define acceptable substitutes
- Engineering Change Orders: Formal ECO process for BOM changes
Extensibility Points
-
Domain Events: Full event-driven architecture for BOM changes
-
Multi-Level Support: Foundation exists for recursive explosion
-
Custom Validation: Domain service extensible for business-specific rules
-
Integration Events: Cross-module communication
- BOMCreated → Production Planning
- ComponentsChanged → Inventory Planning
- BOMArchived → All consuming modules
Last Updated: 2025-10-24 | Status: Production | Version: 1.0