Design Principles
BLOGE operators are the execution units that carry business capabilities through a graph. Good operator design keeps graphs understandable, keeps observability meaningful, and prevents teams from hiding orchestration complexity inside arbitrary Java code.
Single Capability Principle
One operator should encapsulate one capability with a clear SLA.
A well-shaped operator is usually:
- independently testable
- independently observable
- replaceable without changing unrelated graph structure
- reusable across more than one graph
- understandable by both developers and domain owners
Good examples:
FetchUserOperatorRiskAssessmentOperatorRouteToVipAgentOperator
Poor examples:
CopyFieldAtoBOperatorDoEverythingForCheckoutOperatorCallServiceAndAlsoFormatViewModelAndAlsoPersistAuditRecordOperator
Granularity boundaries
Too small
These usually do not deserve their own operator:
- field renaming
- constant injection
- simple type conversion
- assembling a downstream payload from multiple upstream outputs
Prefer input {} expressions or transform blocks for these cases.
Too large
Consider splitting an operator when it:
- hides multiple remote calls with different SLAs
- contains branching behavior that should be visible in the graph
- mixes data access, decision making, and side effects in one opaque block
- has wildly different latency or failure characteristics across internal steps
Operator vs transform
| Use a transform when... | Use an operator when... |
|---|---|
| the work is pure data shaping | the step owns business semantics |
| the result can be derived from existing outputs | the step performs I/O or side effects |
| no separate timeout, retry, or fallback should exist | the step deserves independent resilience or telemetry |
| reuse is mainly about projected fields | reuse is mainly about a business capability |
Reuse layers
BLOGE design guidance works best when operators fall into one of three layers.
| Layer | Description | Typical examples |
|---|---|---|
| Infrastructure | General technical primitives | HTTP call, DB query, cache lookup, message send |
| Capability | Cross-domain business capabilities | risk assessment, notification, approval, fraud check |
| Domain | Context-specific business operations | loan-specific underwriting, claim validation, shipping mode decision |
Prefer composition over duplication. If a domain operator repeats 80% of a capability operator, it probably should delegate instead of reimplement.
Keep business code separate from framework code
BLOGE encourages a clean dependency direction:
Business logic -> Operator contract <- Framework runtime
^
Graph definition + transformIn practice that means:
- operator inputs should carry business data explicitly
GraphContextshould carry request metadata, not hidden business payloads- orchestration concerns such as branching and input assembly belong in the graph layer
- business services should not depend directly on scheduler internals or graph topology classes
Schema governance matters
Operators have data contracts, not just method signatures. As graphs grow, make schema changes intentional:
- adding optional output fields is usually compatible
- deleting or renaming required fields is a breaking change
- changing field types should be versioned deliberately
- downstream consumers should rely on explicit schemas whenever possible
BLOGE's schema system and metadata export exist to make these contracts visible to compilers, Studio, editor tooling, and tests.
Behavioral contracts
An operator should also be explicit about behavior, not just data.
Important questions include:
- Idempotency: is retry safe?
- Side effects: does the operator write externally visible state?
- Failure class: is an error retryable, terminal, or business-domain specific?
- Suspension: can the operator legitimately wait for an external signal or timer?
These answers drive how you configure retry, timeout, fallback, and long-running execution.
Graph complexity guidance
BLOGE graphs should remain readable. When a single graph becomes too broad:
- split the flow into sub-graphs
- isolate reusable branches behind focused operators or graph segments
- keep branch conditions declarative and local
- remove “god graph” patterns where one file tries to model an entire domain platform
The goal is not a maximum node count enforced by style alone, but a graph that still communicates intent to the next engineer and to runtime tooling.
Naming and metadata
Consistent naming makes graphs searchable and tooling-friendly.
Recommended conventions:
- node IDs use business intent:
fetchUser,checkCredit,approveLoan - operator class names use capability intent:
FetchUserOperator,RouteEmergencyCallOperator - DSL transforms use projection intent:
orderSummary,riskSummary - tags and metadata should identify ownership, version, and capability family
Operator design checklist
Before adding a new operator, ask:
- Is this a real capability boundary or just data shaping?
- Would separate timeout, retry, or metrics make sense here?
- Can another graph reuse this capability?
- Does the operator input make dependencies explicit instead of pulling hidden state from context?
- If the graph becomes too dense, should this behavior move into a sub-graph instead?
Next steps
- Learn how data contracts are modeled in Schema & I/O
- See authoring patterns in Operator & Graph
- Explore anti-pattern examples in Example Catalog