How DDDplus Enables Polymorphic Extension Execution with forEachExtension()
DDDplus enables polymorphic extension execution by using dynamic proxies to route calls through multiple extension implementations sequentially, with the forEachExtension() method in BaseRouter serving as the entry point for iterating over matching extensions while applying reduction strategies to control execution flow and aggregate results.
In the funkygao/cp-ddd-framework repository, DDDplus treats extension points as first-class polymorphic collaborators. The BaseRouter class provides a uniform mechanism to discover and execute multiple implementations of a domain extension interface, allowing business logic to vary based on runtime identity while maintaining clean architectural boundaries.
Understanding Extension Points and Routers
In DDDplus, a router represents a single extension point. Each concrete router extends BaseRouter and declares two generic parameters: the extension interface (Ext) and the business identity (Identity). This design allows the framework to locate all Spring beans implementing the extension interface that match the given identity at runtime.
The router acts as a coordinator. It does not contain business logic itself; instead, it delegates to extension implementations discovered by the InternalIndexer. This separation enables polymorphic behavior where different business scenarios execute different code paths through the same uniform interface.
The forEachExtension() Method Overloads
BaseRouter exposes three overloaded versions of forEachExtension() in dddplus-runtime/src/main/java/io/github/dddplus/runtime/BaseRouter.java, each providing different levels of control over the polymorphic execution.
No-Reducer Execution
The simplest overload executes all matching extensions and returns the result of the last one. This is useful when you need side effects across all extensions but do not need to aggregate results.
protected Ext forEachExtension(@NonNull Identity identity) {
return forEachExtension(identity, IReducer.allOf());
}
This method internally delegates to the reducer-based version using IReducer.allOf(), which never stops early and returns null as the reduced result (source: BaseRouter.java L30-L32).
Reducer-Controlled Execution
The second overload accepts an IReducer to control when to stop execution and how to combine results. This enables fail-fast scenarios or aggregation patterns.
protected <R> Ext forEachExtension(@NonNull Identity identity,
@NonNull IReducer<R> reducer) {
return forEachExtension(identity, 0, reducer);
}
The IReducer interface defines two methods: reduce(List<R> accumulatedResults) for combining results, and shouldStop(List<R> accumulatedResults) for determining when to halt iteration (source: BaseRouter.java L44-L46).
Timeout-Protected Execution
The third overload adds a timeoutInMs parameter, aborting the entire chain if total execution exceeds the specified milliseconds. This protects against cascading latency in polymorphic extension chains.
private <R> Ext forEachExtension(@NonNull Identity identity,
int timeoutInMs,
@NonNull IReducer<R> reducer) {
Class<? extends IDomainExtension> extClazz =
InternalIndexer.getBaseRouterExtDeclaration(this.getClass());
return findExtension((Class<Ext>) extClazz, identity,
reducer, defaultExtension(identity), timeoutInMs);
}
All three overloads eventually delegate to this private method, which uses InternalIndexer to resolve the extension interface class and then invokes findExtension() to create the dynamic proxy (source: BaseRouter.java L59-L62).
Dynamic Proxy and ExtensionInvocationHandler
The actual polymorphic execution relies on a JDK dynamic proxy created by ExtensionInvocationHandler in dddplus-runtime/src/main/java/io/github/dddplus/runtime/ExtensionInvocationHandler.java.
When findExtension() is called, it instantiates this handler with:
- The list of effective extensions discovered by
InternalIndexer.findEffectiveExtensions() - The
IReducerinstance - The timeout configuration
The handler creates a proxy implementing the extension interface. When any method on this proxy is invoked, the handler's invoke() method executes the following logic (source: ExtensionInvocationHandler.java L78-L96):
for (ExtensionDef extensionDef : effectiveExts) {
result = invokeExtension(extensionDef, method, args);
accumulatedResults.add(result);
if (reducer == null || reducer.shouldStop(accumulatedResults)) {
break;
}
}
return reducer != null ? reducer.reduce(accumulatedResults) : result;
This loop enables true polymorphism: the same method call (allow(order)) executes across multiple implementations (StockCheckExt, BlacklistExt, etc.), with the reducer determining whether to continue or halt based on intermediate results.
The IReducer Contract for Result Aggregation
The IReducer<R> interface in dddplus-runtime/src/main/java/io/github/dddplus/runtime/IReducer.java defines the contract for controlling polymorphic execution flow (source: IReducer.java L50-L71):
R reduce(List<R> accumulatedResults): Combines all collected results into a final value.boolean shouldStop(List<R> accumulatedResults): Determines whether to stop iterating over remaining extensions.
DDDplus provides two built-in reducer implementations:
| Reducer | Behavior |
|---|---|
IReducer.allOf() |
Executes all extensions, never stops early, returns null (useful for side-effects only). |
IReducer.stopOnFirstMatch(predicate) |
Stops as soon as the predicate matches the latest result; returns that result. |
Custom reducers enable complex aggregation patterns, such as collecting validation errors from all extensions or implementing voting mechanisms across multiple business rules.
Practical Implementation Example
The OrderAllowShipExtRouter in dddplus-test/src/test/java/ddd/plus/showcase/wms/domain/order/ext/OrderAllowShipExtRouter.java demonstrates real-world polymorphic extension execution (source: OrderAllowShipExtRouter.java L11-L28):
@Router
public class OrderAllowShipExtRouter extends BaseRouter<IOrderAllowShipExt, Order> {
public boolean allowShip(Order order) {
// Stop as soon as an extension returns FALSE (disallow)
Predicate<Boolean> stopper = allow -> allow != null && !allow;
Boolean allow = forEachExtension(order, IReducer.stopOnFirstMatch(stopper))
.allow(order);
// No extension → default to true
return allow == null ? true : allow;
}
@Override
public IOrderAllowShipExt defaultExtension(@NonNull Order identity) {
return null; // no default behavior
}
}
In this example, forEachExtension(order, IReducer.stopOnFirstMatch(stopper)) creates a dynamic proxy for IOrderAllowShipExt. When .allow(order) is invoked on the proxy, the ExtensionInvocationHandler iterates through all matching extension beans (such as StockCheckExt or BlacklistExt). If any extension returns false, the reducer stops further execution immediately, implementing a fail-fast validation pattern.
How Polymorphism Is Achieved
DDDplus achieves polymorphic extension execution through four architectural mechanisms:
-
Interface-based contracts – Extensions implement a common Java interface (e.g.,
IOrderAllowShipExt), ensuring type safety while allowing diverse implementations. -
Dynamic proxy pattern – The router never references concrete extension classes. Instead,
ExtensionInvocationHandlercreates a JDK dynamic proxy that forwards method calls to discovered beans at runtime. -
Reducer-driven dispatch – The same
forEachExtensionmethod supports multiple execution semantics—side effects, first-match, or custom aggregation—by accepting differentIReducerimplementations. -
Runtime discovery –
InternalIndexermaintains a registry of extension beans and resolves effective extensions based on the business identity passed to the router, enabling true polymorphism where the execution path varies by context.
These mechanisms collectively provide a uniform, extensible, and type-safe approach to polymorphic extension execution in domain-driven design applications.
Summary
- Polymorphic extension execution in DDDplus relies on the
BaseRouter.forEachExtension()method to coordinate multiple implementations of a domain extension interface. - The method supports three overloads: no-reducer (execute all), with-reducer (controlled execution), and with-timeout (latency protection).
- Dynamic proxies created by
ExtensionInvocationHandlerenable the router to invoke extension methods without knowing concrete implementation classes. - The
IReducercontract determines when to stop iterating over extensions and how to combine their results, supporting patterns like fail-first or full aggregation. - Runtime discovery via
InternalIndexerensures that only extensions matching the current business identity participate in the polymorphic execution chain.
Frequently Asked Questions
How does forEachExtension() handle multiple extension implementations?
forEachExtension() uses the ExtensionInvocationHandler to create a dynamic proxy that iterates over all effective extension implementations discovered by InternalIndexer. When a method is called on the proxy, the handler invokes that method on each extension bean sequentially, collecting results in a list. The process continues until either all extensions have been executed or the provided IReducer signals to stop via its shouldStop() method.
What is the purpose of the IReducer in polymorphic extension execution?
The IReducer serves as a control mechanism for the extension execution loop. It defines two responsibilities: determining when to stop iterating over extensions (shouldStop()) and how to combine multiple results into a single return value (reduce()). This enables different execution semantics—such as fail-fast (stop on first false), collect-all (gather all validation errors), or first-match—using the same forEachExtension() infrastructure.
Can forEachExtension() enforce a timeout across all extension calls?
Yes. The third overload of forEachExtension() accepts a timeoutInMs parameter that applies to the entire execution chain. When specified, the ExtensionInvocationHandler monitors the total execution time across all extension invocations. If the cumulative execution exceeds the specified timeout, the framework aborts the remaining extensions and returns the results accumulated up to that point, protecting the system from cascading latency issues in polymorphic chains.
How does DDDplus discover which extensions to execute for a specific identity?
Extension discovery is handled by InternalIndexer, which maintains a runtime registry of all @Extension annotated beans. When forEachExtension() is invoked with a business identity, InternalIndexer.findEffectiveExtensions() filters the registered extensions to find those whose declared identity constraints match the provided identity object. Only these matching extensions participate in the polymorphic execution, ensuring that business logic varies correctly by context while maintaining type safety through the extension interface contract.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →