Boost Code Reusability with Componentized Workflow Design in Java
This article explains how to improve code readability, reusability, and extensibility by adopting a componentized workflow architecture, detailing three‑layer structures, node abstraction, parameter mapping, and practical Java/Spring XML examples for scalable backend systems.
1. The Most Common Three‑Layer Architecture
We start with the familiar three‑layer structure: biz‑core‑common. Business logic is orchestrated in the biz layer, reusable non‑business logic is extracted to the core layer, and low‑level models, DAO methods, and external service facades reside in the common layer, enabling core methods to be reused across new business features.
2. Why Use Componentized Workflow
When a product’s logic becomes complex and long, and variations emerge as the business evolves, a componentized workflow helps isolate common logic, reduce branching, and improve maintainability. By abstracting each processing stage into independent nodes, developers can reuse core components while handling diverse product requirements.
3. Componentized Workflow Design
3.1 Concept
Each business process is broken into nodes that can be arranged and combined. Nodes are defined in XML and executed sequentially, with a context object passing results between them.
<!-- Process definition -->
<bean id="xxxProcess" class="com.xxx.xxx.component.service.model.ProcessModel">
<property name="name" value="xxxProcess"/>
<property name="nodes">
<list>
<!-- First node -->
<bean class="com.xxx.xxx.component.service.node.wrapper.NodeWrapper">
<property name="node" ref="xxxnode"/>
<property name="nodeParametersMaping" ref="xxxMapping"/>
</bean>
<!-- Additional nodes ... -->
</list>
</property>
</bean>The NodeWrapper represents a single sub‑process. Execution proceeds by iterating through the nodes, using a unified method to run the entire workflow.
public ProcessContext execute(ProcessContext context, ProcessModel process) {
LoggerUtil.info(LOGGER, "Starting process, processName=", process.getName(), ".");
// node processing (strong dependency)
context = nodeProcess(context, process);
// extension processing (weak dependency)
extensionProcess(context, process);
return context;
}3.2 Detailed Node Design
3.2.1 Node Structure
Relationship between ProcessModel and NodeWrapper:
public class ProcessModel extends ToString {
/** Process name */
private String name;
/** List of normal nodes */
private List<NodeWrapper> nodes;
/** List of extension nodes */
private List<ExtensionWrapper> extensions;
} public class NodeWrapper {
/** Business node name */
private String nodeName;
/** Actual node implementation */
private ProcessNode node;
/** Parameter mapping */
private NodeParametersMaping nodeParametersMaping = null;
}The ProcessNode interface defines the executable contract:
public interface ProcessNode {
/**
* Execute node logic.
* @param params input parameters
* @return result of the node
*/
ProcessNodeResult process(Map<String, Object> params);
}Parameter conversion is handled by NodeParametersMaping:
public class NodeParametersMaping {
/** context → node mapping */
private Map<String, String> contextToNode = null;
/** node → context mapping */
private Map<String, NodeToContextMaping> nodeToContextMapping = null;
/** static parameters */
private Map<String, Object> staticParamsToNode = null;
}Mapping from node results back to the workflow context is defined by NodeToContextMaping:
public class NodeToContextMaping {
/** Result code mapping */
private int processCode = -1;
/** Next step handling */
private NodeProcessMethod processMethod = NodeProcessMethod.nextNode();
/** Result → context field mapping */
private Map<String, String> nodeToContext = null;
/** Context → context mapping */
private Map<String, String> contextToContext = null;
/** Static parameters to context */
private Map<String, Object> staticParamsToContext = null;
}Example XML configuration for result handling:
<property name="nodeToContextMapping">
<map>
<entry key="1">
<bean class="com.xxx.xxx.component.service.node.wrapper.NodeToContextMaping">
<property name="processCode" value="101"/>
<property name="processMethod">
<bean class="com.xxx.xxx.component.service.node.wrapper.NodeProcessMethod">
<property name="nodeProcessWay" value="ERROR_NODE"/>
</bean>
</property>
</bean>
</entry>
<entry key="3">
<bean class="com.xxx.xxx.component.service.node.wrapper.NodeToContextMaping">
<property name="nodeToContext">
<map>
<entry key="bizObj.result" value="result"/>
</map>
</property>
</bean>
</entry>
</map>
</property>When the result code is 1, the workflow stops (error node). When the code is 3, the node’s result field result is stored in the context and execution proceeds to the next node.
3.2.2 Parameter Conversion Implementation
The conversion from context to node parameters uses PropertyUtils:
public void toNode(ProcessContext context, Map<String, Object> params)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (MapUtils.isNotEmpty(contextToNode)) {
for (Entry<String, String> en : contextToNode.entrySet()) {
String sourceProperty = en.getKey();
String targetProperty = en.getValue();
Object val = PropertyUtils.getProperty(context, sourceProperty);
PropertyUtils.setProperty(params, targetProperty, val);
}
}
}4. Conclusion
Componentized workflow is a design approach that abstracts each processing stage into reusable nodes, improving code reuse, maintainability, and extensibility. The key is not a specific method but the overall mindset of decoupling and modularizing business logic.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alipay Experience Technology
Exploring ultimate user experience and best engineering practices
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
