How DDDplus Manages Step Dependencies with @Step Annotation: A Complete Guide
The @Step annotation in DDDplus allows developers to declare step dependencies via the dependsOn attribute, but the framework intentionally does not enforce these dependencies at runtime, requiring manual execution order management or custom validation rules.
The funkygao/cp-ddd-framework (DDDplus) provides a structured way to define domain steps using the @Step annotation. While this annotation includes a dependsOn attribute designed to document which steps must execute before others, understanding how the framework actually processes—or ignores—this metadata is critical for building reliable domain workflows.
Understanding the @Step Annotation and dependsOn Attribute
The @Step annotation is defined in dddplus-runtime/src/main/java/io/github/dddplus/annotation/Step.java. It includes several metadata fields, with dependsOn specifically intended to declare execution prerequisites:
public @interface Step {
String value() default "";
String name() default "";
String[] tags() default {};
/** 该步骤依赖哪些其他步骤. 被依赖的步骤先执行,才能执行本步骤 */
Class<? extends IDomainStep>[] dependsOn() default {};
}
The Javadoc explicitly states that depended-on steps must execute first. However, the annotation is marked @Deprecated because the core framework does not currently consume this information automatically. The dependsOn attribute exists primarily as a design-time contract to guide developers and support custom tooling.
How StepDef Registers Steps Without Reading Dependencies
When Spring initializes the application context, DDDplus registers each @Step-annotated bean through the StepDef class located at dddplus-runtime/src/main/java/io/github/dddplus/runtime/registry/StepDef.java. The registration process intentionally extracts only specific fields:
public void registerBean(@NonNull Object bean) {
Step domainStep = InternalAopUtils.getAnnotation(bean, Step.class);
this.name = domainStep.name();
this.tags = domainStep.tags();
// NOTE: the `dependsOn` attribute is *not* stored in StepDef
this.stepBean = (IDomainStep) bean;
this.activity = this.stepBean.activityCode();
this.code = this.stepBean.stepCode();
InternalIndexer.index(this);
}
As shown in the registerBean method, the framework captures name and tags but completely ignores the dependsOn array. Consequently, the internal index (domainStepDefMap) contains no dependency graph information, making automatic reordering impossible during runtime.
Runtime Execution: StepsExecTemplate and DDD.findSteps
The execution flow demonstrates why dependency enforcement does not occur automatically. The DDD.findSteps method in dddplus-runtime/src/main/java/io/github/dddplus/runtime/DDD.java retrieves steps based on the provided activity code and step code list:
public static <Step extends IDomainStep> List<Step> findSteps(
@NonNull String activityCode,
@NonNull List<String> stepCodeList) {
List<StepDef> stepDefs = InternalIndexer.findDomainSteps(activityCode, stepCodeList);
// simply returns the beans in the order requested by the caller
}
The method returns step implementations exactly in the order supplied by the caller, typically a static list defined in a SubmitStep implementation. The StepsExecTemplate then executes these steps sequentially:
List<Step> steps = DDD.findSteps(activityCode, stepCodes);
// steps are executed sequentially (or asynchronously if configured)
No topological sorting, cycle detection, or dependency validation occurs between the findSteps lookup and the execution loop.
ArchitectureEnforcer Limitations
The ArchUnit-based architectural guard in dddplus-enforce/src/main/java/io/github/dddplus/ArchitectureEnforcer.java validates structural rules but does not inspect dependency relationships:
public static final ArchRule domainStepRule() {
return classes()
.that().implement(IDomainStep.class)
.and().doNotHaveModifier(JavaModifier.ABSTRACT)
.should().haveSimpleNameEndingWith("Step")
.andShould().beAnnotatedWith(Step.class)
.as("领域步骤的使用规范");
}
This rule only verifies that step classes are properly annotated with @Step. It does not check that the dependsOn graph is respected or acyclic in the execution plan.
Practical Example: Declaring vs. Enforcing Dependencies
Consider the test examples in dddplus-test/src/test/java/io/github/dddplus/runtime/registry/mock/step/BarStep.java:
@Step(
tags = Steps.Submit.GoodsValidationGroup,
dependsOn = FooStep.class // BarStep declares it depends on FooStep
)
@Slf4j
public class BarStep extends SubmitStep {
@Override public String stepCode() { return Steps.Submit.BarStep; }
// ... execute logic ...
}
Here, BarStep explicitly declares that it requires FooStep to execute first. However, the framework accepts any execution order:
// Correct order - works as intended
List<String> steps = Arrays.asList(
Steps.Submit.FooStep,
Steps.Submit.BarStep
);
// Incorrect order - no runtime error, but logical failure risk
List<String> wrongOrder = Arrays.asList(
Steps.Submit.BarStep, // Executes first despite dependency declaration
Steps.Submit.FooStep
);
If you reverse the order, no exception is thrown; the step simply runs before its declared dependency, potentially causing logical errors such as missing data or invalid state transitions.
How to Enforce Dependencies Manually
Since DDDplus does not provide built-in enforcement, you must implement validation yourself.
Runtime Validation with Topological Sort
You can implement a validation utility that checks the execution list against the annotation metadata before calling StepsExecTemplate:
public static void validateStepOrder(List<Class<? extends IDomainStep>> stepClasses) {
Map<Class<?>, Set<Class<?>>> deps = new HashMap<>();
// Collect dependencies from annotations
for (Class<? extends IDomainStep> stepCls : stepClasses) {
Step ann = stepCls.getAnnotation(Step.class);
if (ann != null) {
deps.put(stepCls, new HashSet<>(Arrays.asList(ann.dependsOn())));
}
}
// Verify order: each step's dependencies must appear earlier
Set<Class<?>> seen = new HashSet<>();
for (Class<?> step : stepClasses) {
for (Class<?> dependency : deps.getOrDefault(step, Collections.emptySet())) {
if (!seen.contains(dependency)) {
throw new IllegalStateException(
step.getSimpleName() + " depends on " + dependency.getSimpleName() +
" which has not been executed yet");
}
}
seen.add(step);
}
}
Call this method inside your custom StepsExecTemplate before the execution loop to achieve runtime enforcement without modifying the core framework.
Compile-Time Validation with ArchUnit
For CI/CD pipelines, implement an ArchUnit rule that validates the dependency graph against your static execution lists:
@AnalyzeClasses(packages = "com.myapp")
public class StepDependencyRuleTest {
@Test
public void stepsMustRespectDeclaredDependencies() {
JavaClasses classes = new ClassFileImporter().importPackages("com.myapp");
// Define your execution order source (e.g., router configuration)
List<String> executionOrder = Arrays.asList("FooStep", "BarStep");
classes
.that().areAnnotatedWith(Step.class)
.should(new ArchCondition<JavaClass>("have dependencies declared before themselves") {
@Override
public void check(JavaClass item, ConditionEvents events) {
Step stepAnn = item.reflect().getAnnotation(Step.class);
for (Class<? extends IDomainStep> dep : stepAnn.dependsOn()) {
int stepIndex = executionOrder.indexOf(item.getSimpleName());
int depIndex = executionOrder.indexOf(dep.getSimpleName());
boolean satisfied = depIndex < stepIndex && depIndex != -1;
events.add(new SimpleConditionEvent(
satisfied,
item.getName() + " depends on " + dep.getSimpleName()
));
}
}
})
.check(classes);
}
}
This approach catches ordering violations during testing rather than production.
Summary
- The
@Stepannotation provides adependsOnattribute indddplus-runtime/src/main/java/io/github/dddplus/annotation/Step.javafor declaring step dependencies, but this is purely documentary. - The
StepDef.registerBeanmethod indddplus-runtime/src/main/java/io/github/dddplus/runtime/registry/StepDef.javaextractsnameandtagswhile intentionally ignoringdependsOn, storing no dependency metadata in the registry. - The runtime execution via
DDD.findStepsandStepsExecTemplateexecutes steps in the exact order provided by the caller, performing no automatic reordering or validation. - The
ArchitectureEnforceronly verifies that steps carry the@Stepannotation, not that dependencies are satisfied. - Developers must manually enforce dependencies by either carefully maintaining execution lists or implementing custom validation utilities that perform topological sorting checks.
Frequently Asked Questions
Does DDDplus automatically reorder steps based on @Step dependsOn?
No, DDDplus does not automatically reorder steps. The dependsOn attribute is stored only in annotation metadata and is not read by StepDef, InternalIndexer, DDD.findSteps, or StepsExecTemplate. Steps execute in the exact order you provide to the execution template.
How can I validate step dependencies before execution in DDDplus?
You can implement a validation utility that reads the dependsOn attribute via reflection and performs a topological sort check against your execution list. Call this validation inside your StepsExecTemplate implementation before invoking the step execution loop, or use ArchUnit rules to validate the dependency graph during CI/CD testing.
Why is the dependsOn attribute deprecated in the @Step annotation?
The attribute is marked deprecated in Step.java because the framework core does not currently consume the dependency information automatically. It exists as a design-time hint for developers and custom tools, but since no automatic enforcement exists, the framework authors marked it to indicate limited runtime support.
Where does DDDplus store step dependency information?
DDDplus does not store dependency information. The StepDef class in dddplus-runtime/src/main/java/io/github/dddplus/runtime/registry/StepDef.java explicitly ignores the dependsOn field during registration, and InternalIndexer indexes steps only by activity code and step code, creating no dependency graph structure.
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 →