Skip to main content

Online edition for BLOGE 0.9.8-RC1 · facts verified 2026-09-15 · 中文

Chapter 8 — Turning a Personal DSL Draft into a Team Asset

Promise: You will follow an order graph through five small commits and see how a draft that merely compiles gains contracts, useful diagnostics, intent, and a reuse boundary—until a teammate can change it safely.

Learning goals

By the end of this chapter, you can:

  1. Explain why zero compiler errors does not mean a DSL file is maintainable.
  2. Turn hidden assumptions into a team contract with graph input, field types, and stable output.
  3. Use invalid-expression-path to locate the path and schema that disagree.
  4. Decide whether an explanation belongs in a /// doc comment or in code-review discussion.
  5. Put shared behavior behind an import alias instead of copying nodes.

This chapter adds one variable

The first seven chapters gave the order flow enough behavior to load a customer, read products, calculate a price, and decide whether to create an order. This chapter adds no new business behavior. It adds one team reality: someone else must safely change this graph next week.

That changes the definition of done. A personal draft only needs its author to remember where ctx.customerEmail came from. A team asset must let the compiler, editor, and reviewer trace that assumption.

Opening problem: why a green draft can worry a reviewer

The first order-summary draft is only a few lines:

graph orderDraft {
transform orderSummary {
userId = ctx.userId
customerEmail = ctx.customerEmail
}
}

Predict before reading on. If compilation reports errors=0 warnings=0, which claims follow?

ClaimProven by zero diagnostics?
The current compiler accepts the syntaxYes
Every caller supplies customerEmailNo
The field may be absentNo
A downstream consumer knows the output shapeNo
A rename six months later will be caughtNo

Green means the compiler found no violation of a declared contract. When no contract exists, many mistakes are not yet checkable.

Figure 8-1: five commits turn hidden assumptions into checkable contracts

Run the whole trajectory first

The book includes five independent snapshots and an RC1 probe. Install core and DSL from the pinned BLOGE submodule, then run it:

cd submodule/bloge
mvn -pl bloge-core,bloge-dsl -am -DskipTests install

cd ../../probes/ch08-dsl
mvn test

The important output observed on 2026-09-14 at BLOGE commit cc38fbe5 is:

commit-1 graph=orderDraft errors=0 warnings=0 firstRule=none
commit-2 graph=orderContractDraft errors=0 warnings=1 firstRule=invalid-expression-path
commit-3 graph=orderTypedDraft errors=0 warnings=0 firstRule=none
commit-4 graph=orderReviewable errors=0 warnings=0 firstRule=none
commit-5 graph=orderTeamAsset errors=0 warnings=0 firstRule=none
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Commit 2 is not worse than commit 1. It turns an invisible assumption into a contract, so the compiler can finally point at the problem.

Commit 1: expose the business path

The first draft projects only userId and an email address. It is useful for a personal experiment: “Can this data flow be expressed?”

It is not ready for a team mainline because ctx is opaque. The compiler cannot distinguish “the caller really supplies a top-level customerEmail” from “the author misremembered customer.email.”

The review result is therefore not “failed.” It is:

syntax accepted
input contract = opaque
safe refactor boundary = unknown

Commit 2: declare the input and invite the error out

The second commit declares the actual order-request shape:

input: OrderRequest

schema OrderRequest {
userId: String
customer: {
name: String
email: String?
}
}

The summary still reads ctx.customerEmail. The compiler now knows that the top level contains only userId and customer, so it reports:

warnings=1
firstRule=invalid-expression-path
Path 'ctx.customerEmail' — field 'customerEmail' not found in graph input schema

That warning gives three coordinates: the root is graph input (ctx), the failing path is customerEmail, and the comparison contract is OrderRequest.

The recovery is not to disable schema validation. Decide whether reality contains a top-level email or a nested customer email, then align the schema and expression.

Commit 3: fix the path and state optionality

In this example, reality says customer.email, and some customers have no email:

transform customer {
name: String = ctx.customer.name
email: String? = ctx.customer.email
}

transform orderSummary {
userId: String = ctx.userId
customerEmail: String? = customer.output.email
}

Compilation returns to zero warnings. Two different relationships are now explicit:

  • A path relationship: orderSummary depends on customer.output.email.
  • A type relationship: String? says that absence is allowed by contract.

Do not make every field optional merely to silence diagnostics. Optionality is a business fact. If order notification requires an email, keep String and fail early when it is missing.

Commit 4: tell the reviewer why the DSL exists

Correct paths still do not explain intent. The fourth commit adds a graph output contract and a responsibility statement:

output: OrderGraphResult

schema OrderGraphResult {
orderSummary: OrderSummary
}

/// Projects boundary fields; it does not make a business decision.
transform orderSummary { ... }

A /// comment enters the AST and attaches to the element that follows, so editors and documentation tools can read it. A regular // comment helps source readers but is not contract metadata.

CommentReview value
/// Creates order summaryLow: the name already says it
/// Projects boundary fields; makes no business decisionHigh: it defines responsibility
/// This is importantLow: nothing can be checked

Good documentation states responsibility and boundary. It does not narrate the syntax.

Commit 5: reuse behavior through an explicit alias

The order graph now needs payment. Copying payment nodes looks quick, but it creates multiple sources of truth for retries, amount checks, and status mapping. The fifth commit extracts a graph and names the dependency:

import "./payment-flow" as paymentFlow

node payment : paymentFlow {
input {
orderId = ctx.orderId
amount = ctx.amount
}
}

An alias is not cosmetic shorthand. It is this file's stable name for an imported capability. The compiler must resolve the relative path, compile the imported graph, validate current bindings against its input schema, and expose its output schema downstream.

Figure 8-2: source, import boundary, diagnostics, and review share one source of truth

If the file is missing, imports form a cycle, or two imports use one alias, compilation should stop before a graph is created. Runtime must not guess what an alias means.

Mechanism: stronger declarations create better feedback

The five commits exercise a feedback pipeline:

source
→ lexer/parser: is the syntax legal?
→ import resolver: which graph is referenced?
→ schema resolver: what do fields and types mean?
→ path/boundary validator: are connections compatible?
→ compiled graph: the runtime model

Later conclusions depend on earlier layers. Valid syntax cannot replace import resolution; a resolvable import cannot replace schema compatibility; schema compatibility cannot prove business correctness.

That is the core value of a team DSL: assumptions that once lived in an author's head become feedback that machines and people can inspect.

Failure experiment: a bad alias is not a retry problem

Keep two imported files unchanged but give both as paymentFlow. RC1 produces the stable diagnostic duplicate-import-alias. Recovery means giving distinct capabilities distinct names, or removing the duplicated source of truth. Retrying runtime execution is meaningless because the graph was never built.

Likewise, invalid-expression-path calls for fixing the contract or path, not adding a node retry. Compile-time feedback and runtime resilience operate in different worlds.

Deliberately moved out of the main story

The complete schema type catalog, derived aliases for bare imports, the import resolver SPI, schema evolution, and ValidatedSchema policy belong in the reference layer. They would interrupt the five-commit story here.

In particular, ValidatedSchema is a Java runtime wrapper that attaches STRICT, COERCE, or LENIENT policy to a schema; it is not new .bloge syntax required in this chapter. The compiler unwraps its expected schema for structural checks. Runtime policy details belong in the DSL engineering reference.

Real-world transfer: from a personal recipe to a restaurant card

A home cook may understand “salt to taste.” Restaurant handoff needs ingredient specification, allergens, output form, reasons for critical steps, and a shared sauce version.

Restaurant handoffTeam DSL
Ingredient listgraph input schema
Finished-dish specificationgraph/output schema
Station instructionsnodes/transforms and dependencies
Why a step exists/// responsibility comment
Shared sauce recipeimported graph plus alias
Test-cook feedbackcompiler/linter diagnostics

The goal is not a longer recipe. It is safe change by someone who knows when they departed from the standard.

Your turn: make five reviewable commits

Choose one .bloge file you maintain. Make one kind of change per commit:

  1. Save the current parseable draft and list its undeclared input assumptions.
  2. Declare graph input and let the compiler expose path problems; do not fix them yet.
  3. Fix only paths and optionality, then record the diagnostic change.
  4. Add a stable output and responsibility boundary; do not paraphrase syntax.
  5. Extract one genuinely shared subflow and connect it through an explicit alias.

Produce one review card: which assumption changed, which diagnostic appeared, and who can review more safely as a result. Stop and split the work if one commit changes a business branch, types, and imports together; otherwise you cannot attribute the feedback.

What this chapter proved—and did not prove

  • Zero diagnostics cover declared contracts only; opaque input can create false calm.
  • Schema makes bad paths, missing fields, and optionality visible to the feedback loop.
  • /// records responsibility and boundary; it does not replace good naming and structure.
  • Import aliases make shared graphs explicit dependencies rather than copied nodes.
  • Syntax, references, structural contracts, and business correctness are successive claims. None substitutes for the next.

This probe proves that the five snapshots produce the recorded RC1 compiler feedback. It does not prove that an imported payment service is available, that the business price is correct, or that a production deployment is safe.

The next chapter adds one variable: Chapter 9 — Tooling Workflow turns the maintainable DSL into a repeatable editor, lint, and review loop before Chapter 10 introduces subgraph boundaries.

Experiment acceptance card

  • Expected and observed: Five commits expose a bad path, repair the type, and consolidate an alias, ending at 0/0 diagnostics.
  • Failure and recovery: Introduce a duplicate alias or bad path; repair one declaration and rerun the parser.
  • Proof boundary: Proves localizable DSL feedback, not a correct business answer.
  • Exercise contract: Team DSL; make one change per commit; deliver five diagnostics; stop at 0 errors/0 warnings with each transition explained.

Exact fact entry points

Coding Agent: Open the versioned task guide.