Intent-Driven BFF Contracts¶
Purpose¶
This chapter defines the required contract-creation workflow for all OVES BFF APIs.
The rule is simple:
- intent defines the object
- intent defines the canonical CRUD usage
- contract is derived from that intent
- implementation conforms to that contract
- consumers and scaffolding use the live BFF that implements that contract
This discipline is mandatory. It is not optional architecture polish.
Why This Exists¶
Without a strict intent-driven workflow, three different truths emerge:
- the intended object model
- the documented/schema contract
- the deployed API behavior
Once that happens, scaffolding, sync tools, and downstream apps start inferring semantics from runtime behavior instead of consuming a stable contract. That is a contract-governance failure.
This risk exists in any BFF domain.
It becomes worse when logically distinct concerns are mixed into one contract surface without an explicit modular design decision.
So intent-driven contract design and modular BFF design must be treated as aligned disciplines:
- intent must define the canonical object and CRUD model
- modular design must define which objects belong together in one BFF module
- implementation must not improvise either decision
Canonical Process¶
For every BFF domain object, follow this sequence:
- Define the object from intent.
- Define the canonical read/write usage.
- Freeze the public vocabulary.
- Derive the contract artifact.
- Implement adapters behind the contract.
- Verify live behavior against the contract.
- Make scaffolding and consumers depend on the live verified contract.
- Treat any drift as a defect.
This process applies per BFF module, not only per broad business domain.
If one business domain needs multiple BFF modules with different usage targets, each module must have its own:
- canonical object set
- canonical CRUD surface
- live verification loop
For the marketing domain, this means product, article, page, and site BFF modules may coexist over the same domain space without being collapsed into one mixed contract.
Step 1: Define the Object from Intent¶
Before schema or implementation work, define:
- object name
- stable identity field
- required fields
- optional fields
- relationship fields
- public presentation fields
- internal audit fields
Examples:
- in a product-oriented module:
ProductProductCollection- in an article-oriented module:
ArticleArticleCollection- in a page-oriented module:
PagePageSection- in a site-oriented module:
SiteNavigationNode
The object definition must answer:
- what this object is
- what fields are public contract
- what fields are staging-only
- what fields are internal-only
Also define whether the object belongs to:
- a product-oriented BFF module
- an article-oriented BFF module
- a page-oriented BFF module
- a site-oriented BFF module
Do not allow module boundaries to remain implicit.
Canonical Object Shape Rule¶
For headless business objects that are intended to persist and be reused across applications, define a canonical object shape early and keep it stable.
This is especially required for marketing-domain objects such as:
ProductProductCollectionArticleArticleCollection- future
Page - future
Site
The exact fields may differ by object type, but the modeling discipline should remain aligned.
At minimum, define the object's equivalents of:
- canonical key
- public slug
- display name or title
- description or body where relevant
- canonical media field or media family where relevant
- publish state where relevant
- SEO metadata where relevant
- audit metadata where relevant
Do not model persistent business objects as ad hoc payload fragments if they are intended to be first-class headless content.
Step 2: Define Canonical CRUD Usage¶
Intent must also define how the API is supposed to be used.
For each object:
- how one object is fetched
- how many objects are listed
- how one object is created
- how one object is updated
- how one object is deleted
- how relationships are changed
This should yield a canonical surface for the specific module being designed.
Example for an article-oriented module:
getArticlelistArticlescreateArticleupdateArticledeleteArticlegetArticleCollectionlistArticleCollectionscreateArticleCollectionupdateArticleCollectiondeleteArticleCollection- relationship mutations for collection membership
If the intended usage is not explicit, implementation will invent it.
If multiple BFF modules target the same business domain, do not share CRUD semantics by assumption. Define the CRUD surface separately for each module.
Step 3: Freeze the Public Vocabulary¶
Before implementation, freeze the public nouns and field names.
Examples:
- if the domain says
heroImage, do not later exposeheroMediain one place andmainMediain another - if the domain says a BoM contains
items, do not later exposepropertyas the line-item carrier unless the contract is formally changed - if the domain says
author, decide whether that means: - public byline string
- public author object
- internal authenticated actor
Public vocabulary must not drift casually.
Identity and Slug Rule¶
For headless content objects that must support frontend state manipulation, public routing, search, or cross-platform hyperlinking, distinguish clearly between:
- stable object identity
- public route identity
The required rule is:
- Every object has a stable canonical key.
- Every routable object may also have a public slug.
- The canonical key is the persistence identity.
- The slug is the public route and hyperlink identity.
Responsibility split:
- the client may supply a proposed slug during create or rename flows
- the BFF may generate a slug if none is supplied
- the BFF must validate and normalize any client-supplied slug
- the BFF must enforce uniqueness and
SLUG_MAX_LENGTH - the BFF is the final authority for the persisted slug
- if a client-supplied or internally generated slug is too long, the BFF must shorten only the readable stem and must not alter the 6-character digest suffix
- when the BFF shortens or otherwise changes a proposed slug, it should surface a mutation warning and return the actual persisted slug
The slug must be:
- readable
- internet-friendly
- deterministic from the object state used to create it
- unique across its object domain
Default slug generation rule:
- take the object title or name as input
- lowercase it
- remove punctuation and other special characters
- normalize remaining text into hyphenated words
- truncate the readable stem as needed
- suffix it with a 6-character hash digest derived from the canonical key
- enforce total length using the global content-system constant
SLUG_MAX_LENGTH
Example pattern:
human-readable-title-1a2b3c
This rule exists because titles can collide and titles can change.
So the operating rules are:
- canonical key remains the authoritative object identity
- slug remains the public-facing route token
- slug uniqueness is guaranteed by the canonical-key-derived hash suffix
- slug sanitization includes punctuation and special-character removal
- total slug length must obey
SLUG_MAX_LENGTH - if shortening is required, preserve the 6-character digest suffix unchanged
- renaming title may regenerate slug only by explicit policy decision
When write APIs need stable targeting, use the canonical key.
When read APIs need public routing or external references, support slug-based lookup as well.
Step 4: Derive the Contract Artifact¶
Only after intent and canonical CRUD are explicit should you write:
- GraphQL schema
- DTO definitions
- mutation input types
- example queries
- example mutation payloads
The schema is not supposed to be creative. It is supposed to encode the intent.
Step 5: Implement Adapters Behind the Contract¶
The BFF exists to absorb backend mess.
That means:
- backend storage limitations must not casually redefine the public contract
- provider naming must not leak into public fields
- partial backend constraints must be handled in adapters, not pushed to consumers as ad hoc API truth
If the backend cannot yet support the intended contract, that gap must be documented explicitly as a temporary limitation.
Step 6: Verify Live Behavior¶
Live verification is mandatory before downstream consumers rely on the contract.
Verification must include:
- introspection or equivalent runtime schema check
- read query verification
- write mutation verification
- relationship mutation verification
- example consumer payload verification
If live behavior differs from intended contract, the system is not “close enough.” It is out of contract.
Step 7: Make Consumers Use the Live Verified Contract¶
Scaffolding, sync tools, and apps must use:
- the live BFF behavior that has been verified
- not stale local assumptions
- not aspirational schema notes
This does not mean local schema is useless.
It means local schema is only useful if:
- it is maintained as a contract artifact
- or it is clearly marked as historical evidence / contract debt
Step 8: Treat Drift as a Defect¶
If any of these disagree:
- intent
- schema/docs
- live API
- scaffolding assumptions
that is a defect.
Do not normalize drift by silently patching consumers forever.
Case Study: Marketing Domain Contract Drift¶
The marketing domain is the working example of what went wrong.
The most important lesson is not just that fields drifted.
The lesson is that:
- product concerns
- article concerns
- and later page and site concerns
were not disciplined strongly enough as explicit BFF modules with explicit canonical contracts.
What the intent should have driven¶
The original intent should have driven:
- one coherent canonical contract per BFF module
- stable object sets inside each module
- stable CRUD usage inside each module
- explicit vocabulary per module
- explicit boundaries between product, article, page, and site concerns
What went wrong¶
The discipline above was not enforced, so three truths emerged:
- local intent/staging assumptions
- local schema/docs in
dirac-fed - deployed live BFF behavior
This allowed:
- mixed expectations about whether one marketing BFF should cover several logically distinct concerns
- flat live article retrieval where local schema expected nested structures
- mutation inputs that omitted intended public author representation
- product composition semantics that drifted away from the intended canonical object model
- collection and article shapes that diverged from local documentation
- scaffolding that had to infer meaning from live runtime instead of consuming a stable contract
Why this is a process failure¶
The failure was not just “the API changed.”
The failure was:
- intent did not become the governing contract
- modular boundaries did not become governing design constraints
- contract artifacts did not govern implementation
- live verification was not treated as a release gate
- downstream tools were forced to reverse-engineer the runtime
What to do next time¶
For future article work, product work, and all other BFF domains:
- decide module boundaries explicitly
- define the object and CRUD semantics first
- freeze the vocabulary
- derive schema and examples from that intent
- implement against it
- verify live behavior before consumers adopt it
- document any temporary contract limitations explicitly
Non-Negotiable Workflow Rules¶
- Never start from provider payload shape when defining the public contract.
- Never let scaffolding infer semantics from undeclared live behavior.
- Never treat stale local schema as authority over live verified API behavior.
- Never allow field renames or semantic changes without an explicit contract update.
- Never hide contract drift inside consumer-side compatibility patches only.
- Always record whether a field is:
- canonical public contract
- temporary local staging field
- internal audit field
- Always verify live BFF before teaching downstream tools what the contract is.
- Always decide whether multiple focused BFF modules are needed before designing a mixed all-in-one API.
- Never split one canonical domain object into weaker public object types only to control recursion or implementation difficulty.
Role of Local Static Schema¶
The local static schema may still be kept for:
- version history
- design evidence
- documentation
- proof of contract debt
But if it differs from the live BFF, it must be treated as:
- out of sync
- non-authoritative for consumer implementation
- a defect to reconcile
Required Outcome¶
From now on, every OVES BFF contract must be:
- intent-first
- vocabulary-stable
- CRUD-explicit
- live-verified
- consumer-safe
That is the baseline contract discipline for this repository.