Online edition for BLOGE
0.9.9-RC1· prepared 2026-11-02 · 中文
Appendix J — BLOGE Coding and Refactoring Handbook
Short code can hide an important decision: a binding queries a remote service, a fallback turns a timeout into a low score, or one approval call also funds and notifies. This handbook starts with those symptoms and explains which BLOGE structure fits and how to verify a behavior-preserving change.
The methods and runnable workshop use the BLOGE 0.9.9-RC1 candidate source. They are design guidance, not universal rules enforced by the engine or evidence that candidate artifacts have been published. Read the chapters in order, look up a BP identifier when making a design choice, and use Chapter 34 to combine the methods.
Choose the expression, then its maintenance scope
| Main responsibility | Preferred expression | Boundary to preserve |
|---|---|---|
| Read or rename an existing field | Input binding | Explicit data source |
| General, lightweight value computation | Registered built-in function | Type, null, numeric, and locale semantics |
| Named projection of several fields | Transform | No hidden external action |
| Bounded computation needs a domain name | Helper; typed function when DSL reuse is needed | Explicit arguments and isolated tests |
| Several facts determine one result | Decision table or domain method | Overlap, gaps, and priority |
| Select the next execution path | Branch | Consume a decision without recomputing policy |
| A capability needs independent execution control | Operator | Input/output, failure, cost, and effects |
| Reuse a sequence of business steps | Subgraph | Explicit input/output and one lifecycle owner |
Pure does not mean cheap. An expensive algorithm can need an Operator boundary; a helper used only inside Java does not automatically need a published DSL function.
| Maintenance scope | Suitable content | Insufficient reason to expand scope |
|---|---|---|
| Application | Company policy and business adapters | Another team might need it |
| Team or domain library | Consumers sharing one contract | Many copies with different policy owners |
| Optional module | Protocols, SDKs, specialized wiring | Installing a provider dependency in core for convenience |
| BLOGE core | Stable cross-domain primitives | Saving one expression line |
BP-01
Check existing functions for general value computation
Symptom: A dedicated Operator trims text, removes duplicates, or concatenates fields.
Change and reason: Inspect the deployed registry/catalog, then check semantics. Bind a single field directly; name related projections with a transform. Existing trim and distinct often remove the need for an independently retryable node. Projection still has evaluation cost.
Verify: Check null, empty collections, order, and invalid types. concat treats null as empty text; that does not satisfy a required-name contract. General round does not define a currency policy.
Exception: Built-ins include environment-dependent now(), today(), and uuid(). Time and IDs use execution services, but are not automatically constant across retries or executions. Functions are not all pure or deterministic.
BP-02
Name bounded computation and keep business arguments explicit
Symptom: Nested expressions obscure evaluation intent or one calculation must be fixed in several places.
Change and reason: Extract an ordinary domain method first. Publish a typed function with @BlogeFunction and @BlogeFunctionLibrary only when graph authors need it. Describe arguments, results, and errors in JavaDoc; declare effects and determinism in metadata. Declarations do not prove behavior.
The workshop's loanText.normalizePolicyCode is an application-defined function, not a BLOGE built-in. It rejects blank codes and uses Locale.ROOT; that contract differs from a conversion that depends on the default locale.
Verify: The workshop provides the complete class, registration, and DSL. Test Turkish default locale, null, blank input, the catalog name, and the projected result.
Exception: Do not extract a function when one trim already communicates intent. Internal Operator helpers need not be published. The registry allows replacement by name, so use an explicit prefix and check collisions.
BP-03
Use an Operator for independent execution responsibility
Symptom: A binding calls customer.currentCredit(id) remotely, a function queries and writes, or expensive computation is hidden in binding evaluation.
Change and reason: Prefer an Operator when the call needs its own timeout, retry, replacement, observations, or capacity policy. A visible CreditScore node lets policy consume credit.output.score. Keep approval, funding, and notification out of one undifferentiated failure boundary.
Verify: Distinguish function invocation, Operator invocation, and real external effects. Call an EffectPort through invoke and test the actual REAL/DOUBLE/DENY wiring. Creating a node does not intercept every internal HTTP or JDBC call.
Exception: Instance function libraries support repositories, clients, and READ_EXTERNAL; using an Operator for I/O is default design guidance, not a syntax restriction. If a read remains a function, explain repeated evaluation, consistency, and control requirements. Conversely, expensive pure computation can justify an Operator.
BP-04
Prove a semantic gap before proposing a public function
Symptom: Company policy is proposed for core, or a public helper only shortens a simple composition.
Change and reason: Application registration, team modules, and default built-ins have different costs. A public proposal needs the same semantics in different business contexts, a concrete limitation in existing compositions, type/null/error/order/numeric contracts, bounded cost, an owner, and a compatibility plan. Provider SDKs and credentials usually belong in optional modules.
Example: distinct exists. Keeping the first item by an explicit business key can be explored locally, but distinctBy here is only a candidate name. Define null keys, first versus last, equality, and order before authoring new syntax.
Verify: Use distinct real consumers and regression-test existing calls. Fixing an implementation that violates a contract differs from changing the contract; null, rounding, or output-shape changes need an explicit migration.
Exception: A bank's eligibility score has a policy owner and version. Repeated use does not turn it into a general built-in; keep it in domain code or a decision table.
BP-05
Separate configuration, adaptation, and new Operator capabilities
| Change | Preferred action | Order example |
|---|---|---|
| Endpoint, credentials, or wiring | Configuration or instance assembly | One payment API in different environments |
| Provider protocol | Narrow port/adapter, optional dependencies | Replace the provider, preserve authorization semantics |
| Business guarantee or result | New capability or explicit version | Model authorization and charging separately |
| Several ordered capabilities | Graph/subgraph | Charge, book, notify |
| Field shape only | Binding/transform | Adapt the provider response |
Symptom: UniversalBusinessOperator(mode, payload) performs queries, approvals, and charges.
Change and reason: Keep the capability name consistent with its contract. A public Operator proposal needs schemas, effects, errors, idempotency, ownership, and real-provider contracts. A connector can be released independently of core.
Verify and exception: Provider adapters can share a capability; authorization and actual charging must not be hidden behind one mode flag. Default UNKNOWN/MIXED declarations do not establish retry safety.
BP-06
Expose multi-fact policy and let routing consume its result
Symptom: More than three independent facts determine one result, or a policy edit requires tracing nested conditions.
Change and reason: List facts, outputs, and priority before evaluating a decision table. A branch consumes the decision. More than three is a review prompt, not an engine threshold: routing four enum values can still use a branch directly.
The loan starter rejects a blacklist hit, credit below 600, fraud at least 80, or weak income. Otherwise it approves credit at least 740 with fraud below 20 and strong income; remaining inputs require review. Chapter 34 moves this decision into a table while preserving seven output fields and three routes.
Verify: Compare fixed CSV answers, before/after results, metadata, SKIPPED states, and simulated action counts. Keeping a short, clear domain method is also a valid outcome when policy is stable.
Exception: Do not force recursive algorithms into rule rows or create a separate table for every simple guard.
BP-07
Explain overlap before choosing a hit policy
| Contract | Policy | Required observation |
|---|---|---|
| Overlap is allowed; earlier rules have priority | first | Reordering can change the decision |
| Rules should be exclusive | unique | Executed overlapping inputs fail |
| Multiple matches must agree | any | Different outputs conflict |
| Every match contributes an item | collect | Empty, multiple, duplicate, and default items |
Example: Credit 760, fraud 10, strong income, and a blacklist hit satisfy rejection and approval predicates. first preserves rejection priority. Replacing it with unique turns a valid rejection into a conflict. unique does not statically prove exclusion over an unlimited input domain.
Verify and exception: Exclusive rules can use unique after validating exclusions and gaps. In the current implementation, collect + otherwise always appends the default item. Add a normal explanation after collection when it is empty, rather than expressing that condition with otherwise.
BP-08
Pass business data explicitly and retain real dependencies
Symptom: Operators read hidden upstream values from shared context, independent checks are serialized, or a join assumes unselected outputs exist.
Change and reason: Bind business input explicitly and reserve context for request metadata. Remove dependencies without a business reason, then join the facts needed for a decision. Add control dependencies for required ordering without a field reference. Thread safety, CPU, and provider capacity remain separate constraints.
Verify: Inspect data sources and execution traces; test optional, missing, and SKIPPED outputs. Approval must precede a charge even if no charge field directly references approval.
BP-09
Give retries one primary owner and keep business keys stable
Symptom: Graph, Operator loop, and SDK each allow three total attempts, or every attempt creates a fresh UUID key.
Change and reason: Select a primary retry layer and coordinate the total budget. The same logical request keeps one key across retry and recovery; a new legitimate request gets a new identity. Execution identity is not automatically business request identity.
Verify: Count underlying attempts and committed effects separately; two layers with three total attempts can multiply to nine. In the Java builder, .retry(1, backoff) allows one retry after the initial attempt: two calls in total. The workshop demonstrates that an EffectPort.idempotencyKey declaration alone does not suppress calls. The provider or application implements deduplication.
0.9.9 boundary: Durable recovery budget is separate from call retry. It tracks consecutive failures without progress; taking a lease again does not constitute committed business progress.
BP-10
Preserve business facts through timeout, fallback, and compensation
Symptom: Credit timeout becomes score zero, payment timeout becomes not charged, or notification failure is described as a full rollback.
Change and reason: Distinguish success, rejection, degradation, and unknown outcomes. A timed-out write may require reconciliation by business key. A fallback needs recognizable degraded semantics. Compensation refers to the original receipt and must represent a business-allowed reversal.
Verify: Separate not executed, committed with response lost, exhausted retries, consumed fallback, failed compensation, and follow-up handling. Matching schemas do not establish equivalent business results.
Exception: Policy may reject when no trustworthy result is available, but must not fabricate a successful credit check. Compensation is not a cross-system database rollback.
BP-11
Make time, waiting, and recovery identity explicit
Symptom: Sleep waits for human approval, each node independently derives a deadline from now, or an in-process resume is called cold recovery.
Change and reason: Use declared suspension and deadlines for long waits, ctx.timeSource() for Operator time, and fixed business deadlines as facts. Test cold recovery using a new runtime and persisted state.
Verify: Cover before, exactly at, and after the deadline plus wrong correlation keys. In 0.9.9 an old worker loses local commit authority when its lease is lost; that does not establish that a remote effect did not happen. Verify checkpoints, leases, and external idempotency separately.
Exception: Bounded short polling can remain when attempts, interval, total deadline, and resource impact are explicit.
BP-12
Reuse multi-step workflows with one lifecycle owner
Symptom: Credit assessment is copied across graphs, subgraphs read arbitrary parent context, or Session and state machine both own the same authoritative state.
Change and reason: Extract a subgraph only around a stable business contract. Pass facts explicitly and return results without exposing internal node IDs. Choose the outer lifecycle owner by the dominant identity; communicate through public signals and terminal results.
Verify and exception: Internal node replacement must not break callers, and recovered signals must reach the correct owner. Similar flows with different policy ownership need not share an abstraction. Fewer classes or graphs do not prove clearer responsibility.
BP-13
Bound batch, loop, and Agent execution
Symptom: Unbounded input is immediately parallelized, a loop runs until success, or an Agent reports normal completion after exhausting turns.
Change and reason: State input size, capacity, business stop condition, and limits separately. Agents also need tool scope and budget. Use parameters supported by each model; do not invent a universal DSL throttling field.
Verify: Test empty, boundary, over-limit, partial failure, and never-terminating conditions. Bounded asynchronous recovery in 0.9.9 is opt-in; a shared budget is process-local. Permits cover actual worker lifetimes: returning from a method or requesting cancellation does not establish resource release.
BP-14
Keep expected answers independent and test forbidden actions
Symptom: Only SUCCESS is asserted, the implementation generates expected values, or a fully mocked Operator is claimed to validate its internal policy.
Change and reason: Test computation in domain methods, dependency and routing in graphs, provider behavior in port contracts, and business promises in Scenarios with an approved Oracle. For a rejected loan, observe the rejected result, approve SKIPPED, and zero simulated approval actions separately.
Verify: The workshop moves approval ahead of blacklist rejection and exposes the changed outcome. Equivalence preserves old behavior; it does not establish that old policy is correct. Sensitivity and bounded property claims remain limited by samples and execution controls.
Capability proposal and refactoring completion card
| Material | Reviewable deliverable |
|---|---|
| Problem and existing capabilities | Current code, real consumers, catalog inspection, composition gap |
| Choice | Expression, execution responsibility, ownership, rejected alternatives |
| Contract | Types, null, errors, effects, determinism, bounded cost, version |
| Compatibility | Existing consumers, migration, maintenance owner |
| Evidence | Fixed answers, boundaries, counterexamples, routes, effects, test scope |
Before submitting, explain whether the change moves data shape, policy, or execution responsibility; what observable benefit the new structure provides; which outputs and external actions must remain; and which failure injection exposes a mistake. Fewer lines or nodes alone do not establish completion.
Complete code belongs to the Chapter 34 asset directory. Commands, twelve standard examples, counterexamples, and validation limits are in the workshop.
Coding Agent: Open the versioned task guide.