# Implementing Custom IReducer to Control Extension Execution Flow in BaseRouter: A Complete Guide

> Master custom IReducer implementation in BaseRouter to control extension execution flow. Learn strategies like shouldStop and reduce for efficient iteration.

- Repository: [Funky Gao/cp-ddd-framework](https://github.com/funkygao/cp-ddd-framework)
- Tags: how-to-guide
- Published: 2026-03-02

---

**To control extension execution flow in BaseRouter, implement the `IReducer` interface with custom `shouldStop()` and `reduce()` methods, or use built-in factories like `stopOnFirstMatch()` to determine when iteration halts and how results aggregate.**

The `cp-ddd-framework` (funkygao/cp-ddd-framework) uses `BaseRouter` as the core abstraction for routing domain logic to extension points. By supplying a custom `IReducer` to the `forEachExtension` method, you dictate exactly how multiple extensions execute and how their results combine into a single return value.

## How BaseRouter Delegates Execution to IReducer

In [`BaseRouter.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/BaseRouter.java) (lines 44–59), the `forEachExtension` method accepts an `IReducer<R>` parameter that controls the entire execution lifecycle:

```java
protected <R> Ext forEachExtension(@NonNull Identity identity,
                                   @NonNull IReducer<R> reducer) {
    return forEachExtension(identity, 0, reducer);
}

```

The actual iteration logic resides in [`ExtensionInvocationHandler.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/ExtensionInvocationHandler.java) (lines 81–95). The handler walks through all effective extensions and consults the reducer after each invocation:

```java
for (ExtensionDef extensionDef : effectiveExts) {
    result = invokeExtension(extensionDef, method, args);
    accumulatedResults.add(result);

    if (reducer == null || reducer.shouldStop(accumulatedResults)) {
        break;                     // stop condition controlled by reducer
    }
}
return reducer != null ? reducer.reduce(accumulatedResults) : result;

```

This two-phase approach—**accumulate** then **decide**—means your custom `IReducer` determines both the early-termination policy via `shouldStop()` and the final aggregation logic via `reduce()`.

## Built-in IReducer Factory Methods

The [`IReducer.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/IReducer.java) interface (lines 50–71 and 88–99) provides static factories for common execution patterns.

### Stop on First Match

`IReducer.stopOnFirstMatch(Predicate<R>)` halts execution as soon as the **last** extension result matches the supplied predicate, returning that specific result. If no match occurs, it returns the final extension’s result.

**Use case:** Fail-fast validation pipelines where the first `false` or error response should abort further processing.

### Run All Extensions (No Aggregation)

`IReducer.allOf()` never triggers early termination; its `reduce()` implementation always returns `null`. Consequently, `BaseRouter` returns the **raw result of the last executed extension**, effectively ignoring aggregation.

**Use case:** Scenarios where side effects matter but only the final extension’s return value is relevant.

### Legacy First Match (Deprecated)

`IReducer.allOf(Predicate<R>)` executes all extensions unconditionally but returns only the **first** result satisfying the predicate. This API is deprecated; prefer `stopOnFirstMatch` for new implementations.

## Custom IReducer Implementation Strategies

When built-in factories lack the specificity your domain requires, implement `IReducer` directly to define custom stopping conditions and reduction algorithms.

### Strategy 1: Aggregate All Results into a Collection

Implement a reducer that flattens multiple list outputs into a single unified collection by never stopping early and combining results in `reduce()`:

```java
IReducer<List<String>> collectAll = new IReducer<>() {
    @Override
    public List<String> reduce(List<List<String>> accumulatedResults) {
        // Flatten the list-of-lists produced by each extension
        return accumulatedResults.stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());
    }

    @Override
    public boolean shouldStop(List<List<String>> accumulatedResults) {
        // Execute every available extension
        return false;
    }
};

```

**Application:** Extensions each return partial identifier lists; the router merges them into a comprehensive result set.

### Strategy 2: Stop on First Error

Leverage the built-in factory for boolean pipelines where `false` indicates failure:

```java
IReducer<Boolean> stopOnError = IReducer.stopOnFirstMatch(result -> !result);

```

If any extension returns `false` (error), iteration stops immediately and that value propagates upward. If all return `true`, the final `true` is returned.

**Application:** Multi-layer permission checks or validation rules where the first denial should prevent subsequent evaluations.

### Strategy 3: Compute Maximum Value Across Extensions

Create a reducer that collects all numeric priorities and selects the highest value:

```java
IReducer<Integer> maxReducer = new IReducer<>() {
    @Override
    public Integer reduce(List<Integer> accumulatedResults) {
        return accumulatedResults.stream()
            .max(Integer::compareTo)
            .orElse(null);
    }

    @Override
    public boolean shouldStop(List<Integer> accumulatedResults) {
        // All extensions must execute to determine the true maximum
        return false;
    }
};

```

**Application:** Auction or bidding extensions where each proposes a priority score, and the router selects the winner with the highest value.

## Wiring Custom Reducers into Your Router

Subclass `BaseRouter` and override extension methods to inject your reducer into the execution flow. The following example from the framework’s routing pattern demonstrates stopping on the first denial:

```java
public class OrderAllowShipRouter extends BaseRouter<OrderAllowShipExt, OrderIdentity> {
    @Override
    public OrderAllowShipExt defaultExtension(@NonNull OrderIdentity identity) {
        return OrderAllowShipExt::allow; // Fallback that always permits
    }

    public boolean allowShip(Order order) {
        // Stop immediately if any extension denies shipment (returns false)
        IReducer<Boolean> stopIfDenied = IReducer.stopOnFirstMatch(allowed -> !allowed);
        return forEachExtension(new OrderIdentity(order), stopIfDenied);
    }
}

```

According to the source code in `dddplus-runtime`, this pattern appears in concrete routers like `OrderAllowShipExtRouter`, which use `stopOnFirstMatch` to implement circuit-breaker logic across multiple shipping policy extensions.

## Summary

- **BaseRouter** delegates iteration control to `ExtensionInvocationHandler`, which respects `IReducer.shouldStop()` for early termination and `IReducer.reduce()` for final result calculation.
- **Built-in factories** (`stopOnFirstMatch`, `allOf`) handle common patterns like fail-fast validation or last-value-wins execution without custom code.
- **Custom implementations** require only two methods: `shouldStop()` to determine when to break the loop, and `reduce()` to aggregate accumulated results.
- **File references:** [`BaseRouter.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/BaseRouter.java) (lines 44–59), [`ExtensionInvocationHandler.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/ExtensionInvocationHandler.java) (lines 81–95), and [`IReducer.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/IReducer.java) (lines 50–71, 88–99) contain the core implementation details.

## Frequently Asked Questions

### What interface methods must I implement for a custom IReducer?

You must implement `R reduce(List<R> accumulatedResults)` to define final aggregation logic, and `boolean shouldStop(List<R> accumulatedResults)` to determine whether the extension iteration should halt early. Both methods receive the growing list of results from preceding extension calls.

### How does stopOnFirstMatch differ from the deprecated allOf(Predicate)?

`stopOnFirstMatch(Predicate<R>)` stops execution immediately when a result matches the predicate, returning that matching value. The deprecated `allOf(Predicate<R>)` executes **all** extensions regardless of matches, then returns the first matching result from the complete list. The modern API is more efficient for fail-fast scenarios.

### Can I use lambda expressions instead of anonymous classes for IReducer?

Yes, because `IReducer` is a functional interface with two abstract methods. However, since Java functional interfaces require exactly one abstract method, you cannot use a plain lambda for `IReducer` directly. Instead, use the static factory methods like `IReducer.stopOnFirstMatch()` which return pre-built instances, or provide an anonymous class for custom two-method implementations.

### Why does BaseRouter return the last extension result when IReducer.reduce returns null?

When `reduce()` returns `null`, `ExtensionInvocationHandler` returns the variable `result`, which holds the return value of the most recently executed extension. This behavior ensures that `IReducer.allOf()`—which intentionally returns `null` from `reduce()`—naturally yields the last extension’s raw output without additional conditional logic in the handler.