Using plugins
Plugins let you add cross-cutting capabilities (like persistence, metrics, or logging) without pushing that code into every business step. You configure behavior through aspects; the framework wires and generates the integration pieces.
Mental model
- Steps: your business transformations.
- Aspects: where and when a plugin should run.
- Plugins: implementation modules that provide the side effect.
This keeps business logic focused while infrastructure concerns stay declarative.
What you configure
You decide:
- Which plugin aspects are enabled.
- Scope:
GLOBALor selectedSTEPS. - Position:
BEFORE_STEPorAFTER_STEP. - Any plugin-specific parameters.
What TPF handles for you
At build time and runtime, TPF handles:
- Adapter and client/server code generation.
- Transport-specific integration (gRPC/REST/LOCAL).
- Type-aware side-effect wiring.
- Runtime injection for generated plugin surfaces.
Aspect naming and module mapping
Aspect names must be lower-kebab-case and map to the plugin module base name. For example, aspect persistence maps to module persistence-svc. This keeps dependency resolution deterministic.
Side-effect transport contract
Side-effect plugins are modelled as unary services for the selected transport. TPF generates type-indexed service contracts such as ObservePaymentRecordSideEffectService with shape PaymentRecord -> PaymentRecord. It then inserts them at the configured aspect position.
Build-time requirements
- A pipeline YAML config must be discoverable so that output types can be resolved for side-effect adapters. The loader checks module root and
config/forpipeline.yaml,pipeline-config.yaml, or*-canvas-config.yaml. - For gRPC transport, protobuf/descriptor content must include the required
Observe<T>SideEffectServicedefinitions.
Plugin host modules
If you want plugin-server artifacts generated in a dedicated module, add a marker class annotated with @PipelinePlugin("name") in that module. That scopes plugin-server generation there and avoids leaking plugin implementation dependencies into regular service modules.
Example
Without aspect:
{
"steps": [
{
"name": "ProcessOrder",
"cardinality": "ONE_TO_ONE",
"inputTypeName": "Order",
"outputTypeName": "ProcessedOrder"
}
]
}With persistence aspect:
{
"steps": [
{
"name": "ProcessOrder",
"cardinality": "ONE_TO_ONE",
"inputTypeName": "Order",
"outputTypeName": "ProcessedOrder"
}
],
"aspects": {
"persistence": {
"enabled": true,
"scope": "GLOBAL",
"position": "AFTER_STEP"
}
}
}