Skip to main content

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​

Value computation and independent execution responsibility

Main responsibilityPreferred expressionBoundary to preserve
Read or rename an existing fieldInput bindingExplicit data source
General, lightweight value computationRegistered built-in functionType, null, numeric, and locale semantics
Named projection of several fieldsTransformNo hidden external action
Bounded computation needs a domain nameHelper; typed function when DSL reuse is neededExplicit arguments and isolated tests
Several facts determine one resultDecision table or domain methodOverlap, gaps, and priority
Select the next execution pathBranchConsume a decision without recomputing policy
A capability needs independent execution controlOperatorInput/output, failure, cost, and effects
Reuse a sequence of business stepsSubgraphExplicit 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.

Expression form and maintenance scope

Maintenance scopeSuitable contentInsufficient reason to expand scope
ApplicationCompany policy and business adaptersAnother team might need it
Team or domain libraryConsumers sharing one contractMany copies with different policy owners
Optional moduleProtocols, SDKs, specialized wiringInstalling a provider dependency in core for convenience
BLOGE coreStable cross-domain primitivesSaving 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​

ChangePreferred actionOrder example
Endpoint, credentials, or wiringConfiguration or instance assemblyOne payment API in different environments
Provider protocolNarrow port/adapter, optional dependenciesReplace the provider, preserve authorization semantics
Business guarantee or resultNew capability or explicit versionModel authorization and charging separately
Several ordered capabilitiesGraph/subgraphCharge, book, notify
Field shape onlyBinding/transformAdapt 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​

ContractPolicyRequired observation
Overlap is allowed; earlier rules have priorityfirstReordering can change the decision
Rules should be exclusiveuniqueExecuted overlapping inputs fail
Multiple matches must agreeanyDifferent outputs conflict
Every match contributes an itemcollectEmpty, 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​

MaterialReviewable deliverable
Problem and existing capabilitiesCurrent code, real consumers, catalog inspection, composition gap
ChoiceExpression, execution responsibility, ownership, rejected alternatives
ContractTypes, null, errors, effects, determinism, bounded cost, version
CompatibilityExisting consumers, migration, maintenance owner
EvidenceFixed 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.