Architectural Differences Between BaseRouter and BaseDecideStepsRouter in DDDplus
BaseRouter is the abstract generic foundation for all extension-point routing in DDDplus that requires subclasses to implement defaultExtension, while BaseDecideStepsRouter is a deprecated specialization that fixes the extension type to IDecideStepsExt and provides a concrete decideSteps convenience method with a static empty default implementation.
The dddplus-runtime module in the funkygao/cp-ddd-framework repository provides the core plugin architecture for domain-driven design extensions. Understanding the architectural differences between BaseRouter and BaseDecideStepsRouter is essential for implementing proper extension-point routing, as one serves as the universal pluggable foundation while the other represents a legacy convenience layer for specific decision-step workflows.
Core Architectural Distinctions
Intent and Design Philosophy
BaseRouter (dddplus-runtime/src/main/java/io/github/dddplus/runtime/BaseRouter.java) serves as the universal routing engine for any extension point in the DDDplus framework. It encapsulates the generic mechanics of locating, iterating, and reducing extensions based on a business Identity, leaving the concrete business semantics entirely to subclasses.
BaseDecideStepsRouter (dddplus-runtime/src/main/java/io/github/dddplus/runtime/BaseDecideStepsRouter.java) is a thin convenience layer built specifically for the IDecideStepsExt extension point. It provides a high-level decideSteps method that internally delegates to firstExtension(identity).decideSteps(...), hiding the generic routing details behind a domain-specific API.
Generic Type Constraints
The generic signatures reveal the fundamental architectural split:
- BaseRouter:
abstract class BaseRouter<Ext extends IDomainExtension, Identity extends IIdentity>remains fully generic on both the extension type and identity type. - BaseDecideStepsRouter:
abstract class BaseDecideStepsRouter<Identity extends IIdentity> extends BaseRouter<IDecideStepsExt, Identity>fixes theExtparameter toIDecideStepsExt, constraining the router to only handle decision-step extensions.
Extension Contract Implementation
In BaseRouter.java at line 100, the defaultExtension method is declared abstract, forcing every concrete subclass to provide a domain-specific fallback implementation when no matching extension is found.
Conversely, BaseDecideStepsRouter.java at lines 35-38 provides a static empty implementation (EmptyExt) that returns an empty list of steps. This ensures callers never receive a null proxy, though the class is marked @Deprecated at line 20 because the framework now encourages developers to use the generic BaseRouter directly rather than this specialized wrapper.
Source Code Implementation Details
BaseRouter Foundation
BaseRouter provides three primary routing utilities:
forEachExtension(identity)– iterates all matching extensions with optional timeout and reduction strategies.firstExtension(identity)– retrieves the first matching extension.defaultExtension(identity)– must be implemented by subclasses (abstract method at line 100).
The class also includes built-in support for per-router timeout configuration and custom reduction logic via IReducer, implemented in the private method forEachExtension(identity, timeoutInMs, reducer) at line 59.
BaseDecideStepsRouter Specialization
BaseDecideStepsRouter inherits all timeout and reduction capabilities from BaseRouter but exposes a simplified public API:
decideSteps(identity, activityCode)– high-level business method that internally callsfirstExtension(identity).decideSteps(...).
Because it extends BaseRouter<IDecideStepsExt, Identity>, subclasses can only plug in implementations of IDecideStepsExt, and the decision-step logic is fixed to returning a List<String> of step codes.
Practical Implementation Patterns
Extending BaseRouter for Custom Extension Points
When implementing a router for a custom extension point, you must provide the concrete extension type and implement the abstract defaultExtension method:
public class OrderAllowShipRouter
extends BaseRouter<IOrderAllowShipExt, OrderIdentity> {
@Override
public IOrderAllowShipExt defaultExtension(@NonNull OrderIdentity identity) {
return new DefaultOrderAllowShipExt(); // fallback logic
}
public boolean allowShip(@NonNull OrderIdentity id) {
// Execute all matching extensions; stop when one returns true
return forEachExtension(id, IReducer.firstTrue()).allowShip(id);
}
}
Key implementation points:
- Declare the concrete extension type (
IOrderAllowShipExt) in the class signature. - Provide a defaultExtension implementation as required by the abstract base class.
- Use the generic
forEachExtensionwith a customIReducerto implement early-exit behavior.
Legacy Usage of BaseDecideStepsRouter
While deprecated, existing code may still use this router for decision-step workflows:
public class OrderStepRouter
extends BaseDecideStepsRouter<OrderIdentity> {
@Override
public IDecideStepsExt defaultExtension(@NonNull OrderIdentity identity) {
// Inherit the empty implementation or supply a real one
return super.defaultExtension(identity);
}
}
Client code interacts through the simplified API:
OrderIdentity id = OrderIdentity.of(orderId);
List<String> steps = new OrderStepRouter().decideSteps(id, "CREATE_ORDER");
Because BaseDecideStepsRouter fixes the extension type, the router only needs to supply (or inherit) a default extension, and the public API exposes only the decideSteps method.
Summary
- BaseRouter is the universal, pluggable routing engine for any DDDplus extension point, requiring subclasses to define both the extension type and default implementation.
- BaseDecideStepsRouter is a deprecated convenience wrapper that fixes the extension type to
IDecideStepsExtand provides a static empty default implementation to prevent null proxies. - BaseRouter exposes generic iteration utilities (
forEachExtension,firstExtension) with full support for timeout and reduction strategies viaIReducer. - BaseDecideStepsRouter offers a single high-level method
decideStepsthat hides the underlying routing mechanics, but the framework recommends migrating to directBaseRouterusage. - Both classes rely on
RouterDef.javafor runtime registry linking and participate in the same extension discovery mechanism, thoughBaseDecideStepsRouterconstrains extensibility to decision-step scenarios only.
Frequently Asked Questions
Why is BaseDecideStepsRouter marked as deprecated?
According to the source code in BaseDecideStepsRouter.java at line 20, the class is annotated with @Deprecated because the DDDplus framework evolution favors using the generic BaseRouter directly for all extension points. The specialized router added unnecessary abstraction when the generic foundation already provides sufficient flexibility for decision-step routing through the standard extension mechanism.
Can I use BaseRouter instead of BaseDecideStepsRouter for decision steps?
Yes, and this is the recommended approach. You can extend BaseRouter<IDecideStepsExt, YourIdentity> directly, implementing the defaultExtension method to return either a real implementation or an empty list. This approach provides full control over reduction strategies and timeout configurations that BaseDecideStepsRouter obscures behind its simplified decideSteps facade.
What is the EmptyExt implementation in BaseDecideStepsRouter?
EmptyExt is a static inner class defined in BaseDecideStepsRouter.java (lines 35-38) that implements IDecideStepsExt and returns an empty list from its decideSteps method. This singleton pattern ensures that routers always have a valid extension instance to invoke, preventing NullPointerException when no domain-specific extension is registered, though it effectively results in a no-op execution.
How do reduction strategies differ between these routers?
BaseRouter explicitly supports custom reduction via the IReducer interface, allowing implementations to specify early-exit conditions (such as IReducer.firstTrue()) when iterating extensions. BaseDecideStepsRouter inherits these capabilities internally but does not expose them through its public API; the decideSteps method simply delegates to firstExtension, which returns only the first matching extension without applying complex reduction logic across multiple extensions.
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 →