Skip to main content

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

  1. 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.

  2. 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").

  3. 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.

  4. 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.

  5. Positive Quantities: All component quantities must be positive (greater than zero). Zero or negative quantities are logically invalid for component requirements.

  6. Valid Component Items: All component items must exist, be active, and typically should be stockable items since they are consumed from inventory.

  7. 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 BOM
  • ParentItem: Navigation property to the parent Item entity

Production Specification:

  • ProducedUnitOfMeasureId: Foreign key to the unit of measure produced
  • ProducedUnitOfMeasure: 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 identifier
  • CreatedDate: Timestamp of BOM creation
  • ModifiedDate: Timestamp of last modification
  • IsActive: Soft delete flag

Key Behaviors:

Factory Methods:

  • Create(): Static factory method creating new BOM with validation

Component Management:

  • AddComponent(): Adds a single component with validation
  • SyncComponents(): 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 line
  • BillOfMaterialId: Foreign key to parent BOM
  • ComponentItemId: Foreign key to component item
  • ComponentItem: Navigation property to component Item entity
  • Quantity: Amount of component required per unit of parent
  • UnitOfMeasureId: Unit of measure for the component quantity
  • UnitOfMeasure: Navigation property to UnitOfMeasure entity

Key Characteristics:

  1. Immutability: Lines are immutable after creation for audit trail integrity
  2. Owned Entity: Lines cannot exist independently of their parent BOM
  3. Simple Structure: Contains only essential component specification data
  4. 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:

  1. Remove old line from collection
  2. Create new line with updated values
  3. Add new line to collection
  4. 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 required
  • Quantity: 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 items
  • GetAllAsync(): Retrieves all active BOMs
  • GetByParentItemAsync(Guid parentItemId): Finds BOM for specific parent item
  • GetArchivedAsync(): Retrieves archived (inactive) BOMs

Component Query Methods:

  • GetByComponentItemAsync(Guid componentItemId): Finds all BOMs using specific component
  • GetBOMsWithComponentsAsync(): Loads BOMs with full component item details
  • GetComponentUsageCountAsync(Guid componentItemId): Counts how many BOMs use component

Validation Methods:

  • ExistsByParentItemAsync(Guid parentItemId): Checks if parent item has BOM defined
  • HasBOMAsync(Guid parentItemId): Alias for existence check
  • IsComponentUsedAsync(Guid componentItemId): Verifies if item is used as component anywhere
  • CanDeleteBOMAsync(Guid bomId): Validates BOM can be safely deleted

Parent Item Queries:

  • GetParentItemsWithBOMsAsync(): Gets list of items that have BOMs defined
  • GetManufacturableItemsAsync(): Gets items that can be assembled (have BOMs)

Command Methods:

  • AddAsync(BillOfMaterial bom): Persists new BOM with all lines
  • Update(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

  1. Multi-Level BOMs: Recursive explosion through sub-assemblies
  2. BOM Versioning: Full version control with effective dating
  3. Alternate BOMs: Multiple production methods for same item
  4. Co-Products/By-Products: Support for multiple outputs
  5. Scrap Factor: Built-in waste/scrap calculations
  6. Operation Routing: Link BOMs to production routings and work centers
  7. Component Substitutions: Define acceptable substitutes
  8. Engineering Change Orders: Formal ECO process for BOM changes

Extensibility Points

  1. Domain Events: Full event-driven architecture for BOM changes

  2. Multi-Level Support: Foundation exists for recursive explosion

  3. Custom Validation: Domain service extensible for business-specific rules

  4. 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