Mastering Data Aggregation: How Strategy & Command Patterns Saved Our Lineage Tracking

A practical look at applying the command pattern to real-world request processing pipelines.


I am a big fan of design patterns. When I make observations, I try and look for patterns, it's in my nature. As an first hand survivor of the original J2EE effort, I have seen where these have been succesfully applied and (mostly) misued.

Design patterns provide a shared language for complex systems. While textbooks often show simple, idealized examples, their true value shines when decoupling dense, business-critical pipelines.

In this article, we’ll look at a practical, real-world application of the Command pattern used to solve a data-reduction problem in large-scale manufacturing. In short, how to apply a approach to make a large code base focued on a specific problem/solution. This is based on an experience at a previous role.

Background

When processing high-volume manufacturing data, there’s a constant tug-of-war between data fidelity and system performance.

In a recent beverage manufacturing platform project, we faced a unique challenge: tracking orange juice lineage for consumer labeling compliance while keeping record counts manageable for downstream analytics. Here is how we used object-oriented design patterns to solve a complex data-reduction problem cleanly and maintainably.

Both are behavioral design patterns, but they solve entirely distinct architectural problems.

The Strategy and Command Patterns

I will attempt to digest theory into practical application here, bear with me,

The strategy pattern

Strategy Pattern — Generic Template
Strategy Pattern — Generic Template
Strategy Pattern — ReductionRule
Strategy Pattern — ReductionRule

The command pattern

Command Pattern — ReductionCommand / RecordReducerInvoker
Command Pattern — ReductionCommand / RecordReducerInvoker

Applied Data Aggregation: How Strategy & Command Patterns Saved Our Lineage Tracking

When processing high-volume manufacturing data, there’s a constant tug-of-war between data fidelity and system performance.

In a recent beverage manufacturing platform project, we faced a unique challenge: tracking orange juice lineage for consumer labeling compliance while keeping record counts manageable for downstream analytics. Here is how we used object-oriented design patterns to solve a complex data-reduction problem cleanly and maintainably.

The Problem: Detailed Lineage vs. Visual Clutter

When collecting juice at an industrial scale, every batch carries rich metadata used to back up label claims like "100% Organic, Central Florida, ABC County, Valencia Orange Juice".

GranularityExample Label ClaimData Fidelity
High (Ideal)100% Central Florida, ABC County, OrganicHighest
Medium100% Florida Orange JuiceMedium
Low (Fallback)Orange JuiceBaseline

However, as batches mix, tracking every individual micro-lot creates a combinatorial explosion—often exceeding 600+ records per batch. Downstream systems and label-printing APIs could not handle this level of granularity.

We needed a system to progressively combine and generalize records in the least destructive manner possible until the total record count dropped below a configurable threshold (e.g., maximum 50 records).

The Core Reduction Algorithm

To minimize impact on data fidelity, we couldn't just sweep through and merge everything aggressively. We needed to apply rules incrementally by percentage significance threshold, stepping up in tiny increments (e.g., 0.000001%).

double threshold = 0.000001;
while (records.size() > MAX_RECORDS) {
    for (ReductionRule rule : reductionRules) {
        records = rule.apply(records, threshold);
        if (records.size() <= MAX_RECORDS) break;
    }
    threshold += 0.000001;
}

Because each rule contained complex grouping logic (e.g., combining by varietal vs. combining by location), putting all this logic into a single monolithic loop or within the ProductRecord class itself was out of the question.

Architectural Solution: Strategy Meets Command

We separated the data structures from the reduction algorithms using standard design patterns.

1. Encapsulating Algorithms (Strategy Pattern)

Each consolidation rule implements a common interface. This allows us to reorder, add, or test rules independently without touching the core execution loop.

strategy.txtse/visitor-strategy-pattern-applied/strategy.txt
public record ProductRecord(
    BigDecimal percent,
    String varietal,
    String location,
    BigDecimal sugarCount,
    boolean organic
) {}

public interface ReductionRule {
    List<ProductRecord> apply(List<ProductRecord> records, BigDecimal targetPct);
}

2. Executing, Auditing, and Undoing Reductions (Command Pattern)

Each reduction step needs to be a reversible, loggable unit of work — compliance audits need to see exactly which consolidation actions were applied to a batch, and a step that overshoots the target record count needs to be safely rolled back. We wrap each action in a Command, invoked and tracked by an Invoker that keeps a history for undo.

command.txtse/visitor-strategy-pattern-applied/command.txt
// The Command interface
public interface ReductionCommand {
    void execute();
    void undo();
    List<ProductRecord> getResult();
}

// Concrete Command that merges records by Location below a threshold
public class MergeByLocationCommand implements ReductionCommand {
    private final List<ProductRecord> records;
    private final BigDecimal targetPct;
    private List<ProductRecord> before;
    private List<ProductRecord> after;

    public MergeByLocationCommand(List<ProductRecord> records, BigDecimal targetPct) {
        this.records = records;
        this.targetPct = targetPct;
    }

    @Override
    public void execute() {
        before = new ArrayList<>(records);
        Map<String, ProductRecord> grouped = new HashMap<>();

        for (ProductRecord record : records) {
            if (record.percent().compareTo(targetPct) <= 0) {
                // Merge logic based on location generalization
                grouped.merge(record.location(), record, this::mergeRecords);
            } else {
                // Keep record intact
                grouped.put(UUID.randomUUID().toString(), record);
            }
        }

        after = new ArrayList<>(grouped.values());
    }

    @Override
    public void undo() {
        // Restores the pre-execute snapshot when a step overshoots the target
        after = before;
    }

    @Override
    public List<ProductRecord> getResult() {
        return after;
    }

    private ProductRecord mergeRecords(ProductRecord r1, ProductRecord r2) {
        return new ProductRecord(
            r1.percent().add(r2.percent()),
            r1.varietal().equals(r2.varietal()) ? r1.varietal() : "Mixed Varietal",
            r1.location(), // Preserved
            r1.sugarCount().add(r2.sugarCount()).divide(BigDecimal.valueOf(2)),
            r1.organic() && r2.organic() // Lose organic status if either is non-organic
        );
    }
}

The Execution Pipeline

With the patterns in place, the orchestrator loop becomes clean, testable, and robust:

pipeline.txtse/visitor-strategy-pattern-applied/pipeline.txt
public class RecordReducerOrchestrator {
    private final List<ReductionRule> rules;
    private final int maxThreshold;

    public RecordReducerOrchestrator(List<ReductionRule> rules, int maxThreshold) {
        this.rules = rules;
        this.maxThreshold = maxThreshold;
    }

    public List<ProductRecord> reduce(List<ProductRecord> inputRecords) {
        List<ProductRecord> currentRecords = new ArrayList<>(inputRecords);
        BigDecimal targetPct = new BigDecimal("0.000001");
        BigDecimal step = new BigDecimal("0.000001");

        while (currentRecords.size() > maxThreshold && targetPct.compareTo(BigDecimal.valueOf(100)) <= 0) {
            for (ReductionRule rule : rules) {
                currentRecords = rule.apply(currentRecords, targetPct);
                
                if (currentRecords.size() <= maxThreshold) {
                    return currentRecords;
                }
            }
            targetPct = targetPct.add(step);
        }

        return currentRecords;
    }
}

Key Engineering Takeaways

  1. Separation of Concerns: ProductRecord remains a pure data holder. Algorithms for merging location, organic status, or sugar counts live entirely within dedicated rules and commands.
  1. Least Destructive Loss: By looping with tiny percentage increments (0.000001%), low-volume outlier records are generalized first, protecting the high-fidelity data of primary ingredients.
  1. Extensibility: Adding a new business rule (e.g., combineBySugarContent) requires zero changes to the orchestration loop or existing rules—just implement ReductionRule and append it to the rule engine list.
Handler Chain Diagram
Handler Chain Diagram

References

https://softwarepatternslexicon.com/mastering-design-patterns/behavioral-design-patterns/command-strategy-pattern-applied-pattern/