Modular BFF Design: Marketing Worked Example¶
Purpose¶
This chapter applies a general modular BFF design principle to the marketing domain as a worked example.
The principle is general and is not limited to marketing.
The marketing split is the current concrete case study documented in this repository.
dirac-fed is intended to use the same design discipline for other UXI-facing
DIRAC capability areas over time as they are folded into FED, including
dashboards and the PA.
The most important correction is this:
- a BFF is not defined only by domain
- a BFF is also defined by usage context
- multiple BFF modules may target the same business domain
For the marketing domain, this means the correct target architecture is not one mixed BFF. It is a set of focused BFF modules over the same domain space:
Contact BFFProduct BFFArticle BFFPage BFFSite BFF
These modules may share vocabulary, identity rules, auth rules, and provider infrastructure, but they must not casually merge object models or CRUD surfaces.
General Rule¶
A BFF should be modular enough that it can be understood in its entirety.
That is a general rule, not a marketing-only rule.
One broad business domain may legitimately require:
- one BFF module
- or several distinct BFF modules
depending on whether the object models and CRUD semantics remain coherent as one unit.
Why The Marketing Example Exists¶
The earlier marketing product and article work exposed a design failure:
- product objects and item objects were split into different ontological types
- article persistence and article delivery were allowed to drift from the intended content model
- a single BFF surface accumulated mixed responsibilities
- downstream consumers had to infer meaning from implementation details
That must stop here.
The correct rule is:
- keep modules narrow
- keep object models canonical
- keep CRUD explicit
- keep provider mess behind adapters
- do not let one module's compromises leak into another module
Modular BFF Policy¶
For one business domain, you may have multiple BFF modules when the usage contexts are materially different.
Allowed reason to split:
- contact acquisition and lightweight contact lifecycle management are different from SA-governed contact workflows
- product merchandising and product information delivery are different from article publishing
- article publishing is different from page design persistence and site composition persistence
- page automation and site automation need different object models from editorial content
Not allowed:
- duplicating the same object contract with drifting field meanings
- creating separate modules only because the implementation is messy
- leaking provider-specific shapes into public contracts
What "Domain-Targeted" Does Not Mean¶
This guidance must be read carefully because it corrects a recurring misunderstanding.
When we say a BFF is domain-targeted, that does not mean any of the following.
1. It does not mean domain exclusivity to one app¶
Domain-targeted does not mean:
- this BFF can only be used by one app
- this BFF belongs to one frontend only
- reuse across similar apps is a design problem
A BFF should be app-oriented in the sense that it is shaped around a coherent usage context.
But one coherent usage context may legitimately serve multiple similar apps.
Examples:
- one Contact BFF may serve website registration, lead forms, newsletter signup, or social lead ingestion flows
- one Product BFF may serve multiple marketing sites, catalog apps, or commerce-adjacent apps
- one Article BFF may serve website articles, blog surfaces, and social publishing workflows
The rule is not one-app-one-BFF.
The rule is one-coherent-usage-model per BFF module.
2. It does not mean a monolithic BFF for the whole app¶
Domain-targeted does not mean:
- one app must have one giant BFF
- all app concerns must be collapsed into one contract
- product, article, page, and site concerns should be merged just because they appear in the same application
An app may legitimately depend on several logically distinct BFF modules.
The split may be justified by:
- different object models
- different CRUD semantics
- different data sources
- different operational ownership
- different publication lifecycles
The split may be organizational, logical, or data-source dictated.
That is acceptable and often preferable.
3. It does not mean understandability is secondary¶
The goal of a BFF is that it should be understandable in its entirety.
That means:
- the object model should be coherent
- the CRUD surface should be coherent
- the vocabulary should be coherent
- an engineer or agent should be able to understand the module as a whole
When several logically distinct concerns are joined into one BFF, this becomes much harder.
Examples of harmful combinations:
- product plus article in one indistinct contract
- article publishing plus page or site composition in one indistinct contract
- product composition plus editorial distribution semantics in one indistinct contract
These combinations increase confusion because the module stops having one clear conceptual center.
The design target is not maximum consolidation.
The design target is maximum coherent understandability.
Practical review rule¶
When deciding whether one concern belongs in an existing BFF or a separate BFF, ask:
- can one engineer or agent understand this module in its entirety?
- does it still have one clear conceptual center?
- are the object model and CRUD semantics still coherent as one unit?
If the answer starts becoming no, the module is too mixed and should probably be split.
Marketing Domain Case Study¶
The marketing domain in this repo is the active worked example for applying the
modular BFF rule. It is not the full intended scope boundary of dirac-fed.
Required Marketing BFF Modules¶
1. Contact BFF¶
Primary use:
- website registration
- newsletter signup
- lead-capture forms
- social platform lead generation
- OAuth-based first-contact onboarding such as
Login with Facebookor Google sign-in - lightweight contact update and suppression flows
Primary public objects:
ContactConsentExternalIdentity
2. Product BFF¶
Primary use:
- e-commerce-like product information delivery
- product comparison
- product collection browsing
- product detail pages
- product composition and kit/system display
- product data persistence
Primary public objects:
ProductProductCollectionProductMediaAssetPropertyDefinitionPropertyValueBomLine
3. Article BFF¶
Primary use:
- article publishing
- article retrieval
- article collections
- blog feeds
- social posting preparation and distribution metadata
Primary public objects:
ArticleArticleCollectionArticleMediaAssetAuthorProfileor other explicit public author shape
4. Page BFF¶
Primary use:
- page-level persisted configuration
- automated site/app creation
- page composition
- route-level page intent
- page layout persistence
- reusable design primitives
- brochure-page and deck-page purposes
Primary public objects:
PagePageSectionRouteDefinitionContentBindingLayoutPrimitivePageIntentProfile
5. Site BFF¶
Primary use:
- site-level persisted configuration
- multi-page composition
- navigation persistence
- book-like metadata
- cover, index, and table-of-contents style structures
Primary public objects:
SiteSiteCoverNavigationNodeThemeTokenSetSiteMetadataTocStructure
Shared Rule Across All Modules¶
These modules target the same broad business domain, but each module must preserve:
- its own canonical object model
- its own canonical CRUD surface
- its own mutation input types
- its own usage semantics
Shared infrastructure is fine.
Shared public contract by accident is not.
Canonical Marketing Object Shape Rule¶
All marketing-domain object schemas should follow one shared canonical object-shape discipline.
This applies to:
- future
Contact ProductProductCollectionArticleArticleCollection- future
Page - future
Site - and other future marketing-domain objects
The exact field set will differ by object type, but the modeling discipline should stay aligned.
At minimum, every marketing-domain object should define its relevant equivalents of:
- canonical key
- public slug where routing is relevant
- display name or title
- description/body field where relevant
- canonical media field or media family where relevant
- publish state where relevant
- SEO metadata where relevant
- audit metadata where relevant
This rule exists so future marketing objects are modeled as coherent first-class objects rather than ad hoc payload fragments.
For the rest of this chapter, assume:
Contact BFF,Product BFF, andArticle BFFare separate BFF modules- neither module should inherit the other's object model
- shared identity or slug rules may exist across modules, but CRUD and object semantics remain module-specific
Contact BFF Intent¶
The Contact BFF is the persistence and retrieval surface for generic headless contact acquisition and lightweight contact management.
It is intentionally not the SA-governance surface.
Its job is to support contact capture and follow-up in mainstream flows such as:
- website registration
- newsletter signup
- marketing lead capture
- social platform lead generation
- OAuth-based first-contact onboarding
Contact Context Boundary¶
The object anchor remains native Odoo res.partner.
The Contact BFF should create and manage contacts without requiring SA association at creation time.
If a later workflow needs SA governance, that is a separate transition into SA scope, not part of this acquisition contract.
This means:
- acquisition contact flows remain SA-agnostic by default
- acquisition metadata must remain distinct from
{actor, sa}governance metadata - later SA assignment should happen only through an explicit governance operation in another module or workflow
Contact Resolver Instruction¶
This instruction is explicit for BFF developers:
- the underlying provider resolver for
Contactmust target the Odoores.partnermodel
That is the base data source for:
- contact creation
- contact retrieval
- contact update
- contact suppression or archival state
Any helper tables or Odoo extensions for acquisition source, consent capture, verification flags, or external identity reconciliation must remain subordinate to that primary res.partner anchor.
The public Contact contract is headless and provider-agnostic.
The resolver authority is not.
Resolver authority for this module is:
- Odoo
res.partneras the primary system-of-record object
This is different from:
Article BFFPage BFFSite BFF
Those modules are primarily headless content artifacts and are not currently anchored to one established native Odoo business model in the same direct way.
The nearest comparable case is Product BFF, because product data is also expected to resolve against Odoo-backed product records.
However, product still has an open mapping issue around the final authority boundary between the public Product object and the Odoo product substrate, especially regarding linkage to Odoo product variant versus template structures.
Canonical Contact Definition¶
A Contact is a persisted person or organization contact record used for acquisition, registration, opt-in communication, and lightweight commercial follow-up.
In this BFF, the primary target is person-like contacts, but the contract should not block organization contacts.
This object does not itself model:
- SA membership
{actor, sa}governance- PA operating context
- full CRM pipeline semantics
- auth-session or token management
Required Contact Fields¶
At minimum, the canonical Contact contract must support:
contactKeycontactTypedisplayNamefirstNamelastNameemailphonecompanyNamejobTitlecountryCodelocalestatusemailVerifiedphoneVerifiedacquisitionSourcesourceCampaignsourceChannelexternalIdentitytagsconsentsaudit
This module is intentionally simpler than routable content modules.
Therefore:
- no slug is needed
- no SEO fields are needed
- no long description/body is needed
Contact Identity Rule¶
For Contact:
contactKeyis the canonical persistence identity- raw Odoo ids must not be exposed as the public identity
Unlike routable content objects:
- a contact does not require a public slug
Contact Acquisition Metadata Rule¶
The BFF must support explicit acquisition metadata.
At minimum:
acquisitionSourcesourceCampaignsourceChannel
Suggested source vocabulary should start with values such as:
WEBSITE_FORMNEWSLETTER_FORMFACEBOOK_LOGINGOOGLE_LOGINMETA_LEAD_ADMANUAL_IMPORT
Important rule:
- acquisition source is contact metadata
- acquisition source is not SA governance
External Identity Rule¶
The contract should preserve third-party identity references without becoming provider-shaped.
Minimum intended externalIdentity shape:
providerproviderSubjectproviderEmail
This supports:
- Facebook login
- Google login
- third-party lead-form identity reconciliation
Contact Consent Rule¶
Consent must be explicit contract data, not only inferred from tags.
At minimum, each consent line should support:
consentTypegrantedgrantedAtcaptureMethod
This module should be able to preserve at least:
- marketing-email consent
- platform-terms consent
- privacy-policy consent
Contact Tag Rule¶
Tags are appropriate for lightweight segmentation and source annotation.
Examples:
leadnewsletterfacebook-leadself-registered
Tags must not be used to simulate SA governance.
Odoo Mapping Rule¶
The base provider object should remain res.partner.
This is not a soft suggestion.
For implementation purposes:
Contactresolver entry must beres.partner- provider adapters must map from
res.partnerinto the canonicalContactobject - do not invent a separate provider-side contact object unless later design work explicitly changes this contract
Likely mapping direction:
name->displayNameemail->emailphone/mobile-> normalizedphonecompany_type-> helps derivecontactType- tags/categories -> may support
tags
Extension direction may be needed for:
- acquisition source
- source campaign
- source channel
- external identity references
- consent capture metadata
- verification flags
Those are adapter and Odoo-extension concerns, not reasons to distort the public Contact contract.
Contact CRUD Contract¶
Required read operations:
getContact(contactKey)listContacts(filters, pagination, sort)
Required write operations:
createContact(input)registerContact(input)registerContactWithOAuth(input)updateContact(contactKey, input)updateContactConsents(contactKey, input)verifyContactEmail(contactKey, input)verifyContactPhone(contactKey, input)deleteContact(contactKey)archiveContact(contactKey)suppressContact(contactKey, reason)
The module is simple enough that registerContact and createContact may initially resolve to the same provider path, but their intent is different and should remain distinct in the public contract.
Mainstream Contact Scenarios¶
Website Registration Form¶
Typical input:
- first name
- last name
- phone optional
- country optional
- consent checkboxes
Expected behavior:
- create or upsert a
Contact - set
status = REGISTEREDorLEADby policy - set
acquisitionSource = WEBSITE_FORM - apply tags like
self-registered - store consent events
Newsletter Signup¶
Typical input:
- locale optional
- one marketing consent
Expected behavior:
- create or upsert a lightweight
Contact - set
acquisitionSource = NEWSLETTER_FORM - tag with
newsletter - avoid forcing full profile completion
Social Lead Generation¶
Typical source:
- Meta lead ad
- TikTok lead form
- LinkedIn lead form
Expected behavior:
- ingest provider payload through an adapter
- normalize to canonical
Contact - preserve source metadata
- tag appropriately
- keep contact SA-agnostic until a later governed-service workflow exists
OAuth-Backed First Contact¶
Typical source:
Login with FacebookContinue with Google
Expected behavior:
- resolve or create contact using provider identity
- populate
externalIdentity - use provider email if present
- set acquisition source such as
FACEBOOK_LOGINorGOOGLE_LOGIN - mark verification state according to explicit policy, not assumption
Important rule:
- OAuth login is an acquisition and identity event here
- it is not the same as SA membership or PA context establishment
Contact Interface-Type Guidance¶
If interface types are used, the public intent should guide them as:
NodeTaggableAuditableConsentBearing
These are modeling aids only.
The canonical business object remains:
Contact
Contact Input Contract Rule¶
At minimum, create and update inputs must support the relevant subsets of:
contactTypefirstNamelastNameemailphonecompanyNamejobTitlecountryCodelocaletagsconsentssourceCampaignsourceChannelstatus- OAuth provider identity fields where applicable
Relationship to SA Governance¶
If a contact later enters serviced-account governance, that should happen through a separate explicit operation outside this Contact BFF intent.
For the governing Odoo-side design context, see:
Examples of future operations outside this module:
assignContactToServicedAccountsetContactActorAssignmentmoveContactToServicedAccount
Those operations belong to an SA-governed contact workflow, not to generic acquisition.
Product Architecture Intent¶
This section is normative for the marketing-domain Product BFF.
Core Correction¶
The previous Product versus Item split is rejected.
It encoded a false meaning:
Productwas treated as a first-class commercial objectItemwas treated as a lesser material object
That is incorrect for this domain.
The correct model is:
- there is one canonical
Productobject type - some products are higher-level assembled products
- some products are terminal component products
- composition is expressed through product-to-product references
- recursion control is a usage rule, not a different ontology
For current marketing-app purposes, the required public retrieval shape is a 2-level view:
- top-level product
- component products included by that product
That 2-level retrieval requirement does not justify inventing a weaker public Item type.
It is only a retrieval and presentation constraint.
Canonical Product Definition¶
A Product is a commercial or technical object that may appear:
- as a standalone marketed product
- as a component inside another product
- as both
Every Product must be allowed to carry the same core contract fields regardless of level.
Required semantic fields:
- stable identity
- display identity
- description
- media
- classification
- BoP
- BoM
- collection membership
- publication state
The most important modeling rule is:
- component products must remain full products in the public contract
- component products must not be degraded into thin package members with reduced fields
Required Product Fields¶
At minimum, the canonical Product contract must support:
productKeyslugnamesubtitleshortDescriptiondescriptionmainMediamediaGallerypublishStateproductClassisComponentcanAppearStandalonecanBeIncludedInBombopbomcollectionstagsseoaudit
This Product shape is not a special exception.
It is one application of the shared canonical marketing object-shape rule.
Product Identity and Slug Rule¶
For Product:
productKeyis the canonical persistence identityslugis the public route identity
The default slug rule follows the repository-wide headless content policy:
- derive from product title or name
- lowercase it
- remove punctuation and special characters
- normalize remaining text into hyphenated words
- truncate the readable stem as needed
- suffix with a 6-character digest derived from
productKey - enforce total length using
SLUG_MAX_LENGTH
Example:
ovego-e-3-plus-a1b2c3
The reason is to preserve:
- readability
- internet-safe routing
- uniqueness
- stable external hyperlink references
Frontend state and persistence rules:
- update and delete by
productKey - route and public lookup by
slug - client may propose slug
- BFF validates or generates slug and is final authority
Product Level Semantics¶
The contract must support level meaning without creating a second object type.
Examples:
- a scooter can be a product
- a battery can be a product
- a controller can be a product
- a motor can be a product
- a torch can be a product
The distinction is classification, not ontology.
The business question is not:
- is this a different object type?
The business question is:
- what role is this product currently playing?
At minimum, the Product BFF must support fields equivalent to:
isComponent: Boolean!canAppearStandalone: Boolean!canBeIncludedInBom: Boolean!productClass: ProductClass!isSellable: Boolean!
This allows:
- level-1 products
- terminal component products
- products usable in both contexts
without inventing a separate Item contract.
Odoo Mapping Rule¶
Underlying Odoo product and BoM recursion may be deeper than the current marketing-app retrieval shape.
That is acceptable.
The Product BFF must absorb that difference without changing the public ontology.
So:
- deep Odoo recursion is an adapter concern
- public marketing retrieval may still use a 2-level shape for now
- Odoo sellability or related product flags may inform public role fields such as
isSellable - Odoo flags must not by themselves define the public business object model
The public rule remains:
- one canonical
Product - role and sellability are classification concerns
- BoM references other products
Product Resolver Note¶
This instruction should be explicit for BFF developers:
- a
Productis a marketing artifact and should be modeled as a first-class object in its own right
This means the public Product contract should not be reduced to a thin reflection of current Odoo provider structure.
At the same time, a later binding to the underlying Odoo product system must be made.
Current direction:
- every
Productobject is bound to an SKU - that SKU linkage is the bridge to the Odoo product substrate
A useful conceptual helper is:
- think of
Productas a wrapper around an underlying Odoo product object - that wrapper adds metadata and structure in the context of promotion, merchandising, and presentation
This is similar in spirit to how an SA-governed object may wrap an underlying native Odoo object while adding governance meaning that does not belong on the native object alone.
For Product, the wrapper meaning is not governance-first.
It is marketing and presentation-first.
That later bridge is expected to support provider-backed capabilities such as:
- pricing
- inventory
- sellability and fulfillment-related facts
- other commercial or operational fields that belong to the Odoo product system
Important implementation note:
- this resolver-binding functionality is not yet the governing contract of the Product BFF
- it is deferred work that must be added later without collapsing the first-class public
Productobject into raw Odoo shape
So the current intent is:
- public
Productremains the canonical marketing object - later SKU-based linkage connects it to Odoo-backed product facts
- the wrapper adds marketing and presentation meaning around the underlying Odoo product substrate
- the exact final authority boundary across
product.template,product.product, and related Odoo product structures remains to be settled in later implementation design
Bill of Properties Intent¶
BoP is part of the canonical product contract.
BoP must not be modeled as anonymous loose key/value strings only.
The canonical model is:
- a
PropertyDefinitiondefines semantic meaning - a
PropertyValuebinds a definition to a product with a value
Required BoP semantics:
- stable property key
- display name
- typed or classed value
- unit or unit hint
- display order
- optional grouping
The Product BFF may use interface or union strategies internally, but the public contract must preserve:
- property identity
- property meaning
- product-bound value
Bill of Materials Intent¶
BoM is composition of products, not composition of a weaker object type.
The canonical model is:
BomLine { quantity, componentProduct }
where componentProduct is a product reference or product summary shape derived from the same canonical Product object model.
The BFF must preserve enough information for the consumer to render:
- component identity
- component display name
- component description
- component media
- component BoP
- component role classification
- component sellability state when relevant
The BFF may choose summary versus expanded retrieval modes, but the canonical meaning is still:
- a BoM line references a product
not:
- a BoM line references an unrelated
property - a BoM line references a weaker
itemobject with reduced semantics
Recursion Rule¶
Recursive object graphs must be controlled by API design, not by ontology distortion.
Correct control options:
- summary child type for nested references
- depth-limited expansion
- explicit nested fetch endpoint
- field selection discipline
Incorrect control option:
- inventing a different domain object type that strips media, BoP, or other semantics
Product BFF Agent Input Requirements¶
The Product BFF intent must be sufficient for an implementation agent to derive:
- SDL object types
- interfaces or type composition rules
- input types
- query root
- mutation root
- pagination pattern
- relationship mutation pattern
- resolver expectations
To make that possible, the following must be explicit in the intent:
Product Object Contract¶
- canonical object name:
Product - canonical identity field:
productKey - canonical route field:
slug - canonical composition field:
bom.lineItems[].componentProduct - canonical property field:
bop.lineProperties[] - canonical media field family:
mainMedia,mediaGallery
Product CRUD Contract¶
Required read operations:
getProduct(productKey)getProductBySlug(slug)listProducts(filters, pagination, sort)getProductCollection(collectionKey)listProductCollections(filters, pagination)
Required write operations:
createProduct(input)updateProduct(productKey, input)deleteProduct(productKey)publishProduct(productKey)unpublishProduct(productKey)
Required BoP relationship operations:
addProductPropertyLines(productKey, input)updateProductPropertyLine(productKey, lineKey, input)removeProductPropertyLine(productKey, lineKey)replaceProductPropertyLines(productKey, input)
Required BoM relationship operations:
addBomLines(productKey, input)updateBomLine(productKey, lineKey, input)removeBomLine(productKey, lineKey)replaceBom(productKey, input)
Required collection relationship operations:
setProductCollections(productKey, collectionKeys)addProductsToCollection(collectionKey, productKeys)removeProductsFromCollection(collectionKey, productKeys)
Product Interface-Type Guidance¶
If interface types are used, the public intent should guide them as:
Nodefor stable identityPublishableDescribableMediaBearingPropertyBearingComposableProduct
These are interface-level aids, not separate business ontologies.
The canonical business object is still Product.
Article BFF Intent¶
The Article BFF must treat article objects as first-class persisted objects with stable retrieval and publishing semantics.
The Article BFF must be sufficient for:
- own-site article delivery
- article collections
- cross-channel publishing support
- social posting preparation
At minimum, the canonical article contract must make explicit:
- article identity
- article route identity
- title and subtitle
- body
- hero image
- author/byline representation
- collection membership
- tags
- publish state
- collection relationship semantics
Canonical Article Definition¶
An Article is a persisted editorial object intended for publication, retrieval, grouping, and external distribution.
It may appear:
- as a site article
- as a collection member
- as a cross-channel publishing source
- as a reusable knowledge object for assistants and other consumers
Every Article must preserve the same core editorial fields regardless of where it is presented.
Required semantic fields:
- stable identity
- public route identity
- editorial title identity
- body content
- hero media
- byline identity
- publication state
- collection membership
- channel/distribution metadata
Required Article Fields¶
At minimum, the canonical Article contract must support:
articleKeyslugtitlesubtitlebodyheroImageauthorpublishStatepublishedAtupdatedAtcollectionstagsseoaudit
Article Identity and Slug Rule¶
For Article:
articleKeyis the canonical persistence identityslugis the public route identity
The default slug rule follows the repository-wide headless content policy:
- derive from article title
- lowercase it
- remove punctuation and special characters
- normalize remaining text into hyphenated words
- truncate the readable stem as needed
- suffix with a 6-character digest derived from
articleKey - enforce total length using
SLUG_MAX_LENGTH
Example:
offgrid-power-basics-a1b2c3
This supports:
- readable URLs
- internet-safe sharing
- stable crosslinks
- external publishing references such as social posts that carry a slug
Operating rule:
- create or resolve the object by canonical identity
- retrieve publicly by slug when needed
- update by
articleKey - never let slug replace the canonical persistence identity
- client may propose slug
- BFF validates or generates slug and is final authority
- if slug shortening is required, preserve the 6-character digest suffix unchanged
- BFF should return the actual persisted slug and surface a warning when it changes a proposed slug
Author Rule¶
For Article:
authoris a single public string- that string is both the public byline and the lookup key for future external profile enrichment
author.roleis not part of the current Article BFF persistence contractauthor.roleis a page or presentation concern unless and until the Article BFF explicitly models a richer public author object- future richer author handling may use the
authorstring as a lookup key into a separate author-domain object
Article Body Rule¶
The Article BFF must define one explicit editorial body format.
Current intended default:
bodyis markdown content
Style is part of content for marketing-domain editorial objects, so markdown is the canonical persisted body representation.
If richer block content is introduced later, that must be a formal contract evolution, not an ad hoc mixed body shape.
Article Collection Rule¶
Article collections are first-class objects in the Article BFF, not site-owned lists.
An article may belong to zero, one, or many collections.
Collection membership must be handled through explicit Article BFF relationship operations, not hidden page or site state.
Collection membership must also preserve:
- ordered membership
- set-like uniqueness of members
If arrays are used in the API representation, the BFF must still enforce uniqueness semantics.
Article Media Rule¶
For Article, the canonical media shape is:
- one
heroImage
Auxiliary media metadata should come from the media-serving platform, not from a richer article-media object in the current Article BFF contract.
Article Distribution Rule¶
The current Article BFF should treat articles as channel-agnostic headless content.
Channel targeting is not part of the current Article object contract.
Collection usage may support some distribution or targeting concerns, but the Article object itself should remain channel-neutral.
The previous failure on articles came from allowing:
- live payload shape drift
- incomplete author modeling
- mutation inputs that did not preserve intended public semantics
That must not recur.
Article BFF Agent Input Requirements¶
The Article BFF intent must be sufficient for an implementation agent to derive:
- SDL object types
- input types
- query root
- mutation root
- collection relationship operations
- channel-metadata placement
- resolver expectations
To make that possible, the following must be explicit in the intent:
Article Object Contract¶
- canonical object name:
Article - canonical identity field:
articleKey - canonical route field:
slug - canonical title fields:
title,subtitle - canonical body field:
body - canonical hero-media field:
heroImage - canonical byline field:
author
Article CRUD Contract¶
Required read operations:
getArticle(articleKey)getArticleBySlug(slug)listArticles(filters, pagination, sort)getArticleCollection(collectionKey)listArticleCollections(filters, pagination)
Required write operations:
createArticle(input)updateArticle(articleKey, input)deleteArticle(articleKey)publishArticle(articleKey)unpublishArticle(articleKey)
Required collection relationship operations:
createArticleCollection(input)updateArticleCollection(collectionKey, input)deleteArticleCollection(collectionKey)addArticlesToCollection(collectionKey, articleKeys)removeArticlesFromCollection(collectionKey, articleKeys)replaceArticlesInCollection(collectionKey, articleKeys)
ArticleCollection Object Contract¶
ArticleCollection should use the same canonical object-shape discipline as other marketing objects.
At minimum, the canonical ArticleCollection contract must support:
collectionKeyslugnamedescriptionmediaarticlesarticlesCountpublishStateseoaudit
All future marketing-domain objects should start from this same canonical-shape discipline unless there is a clear reason to document an exception.
Article Input Contract Rule¶
At minimum, create and update inputs must support:
titlesubtitlebodyheroImageauthortagscollectionspublishState
If the current live BFF does not yet support all of these fields, that gap must be documented as temporary contract debt, not normalized into consumer-side guesswork.
Article Interface-Type Guidance¶
If interface types are used, the public intent should guide them as:
NodePublishableDescribableMediaBearingTaggableCollectable
These are interface aids only.
The canonical business objects remain:
ArticleArticleCollection
Page BFF Intent¶
The Page BFF is the persistence target for page objects that currently live only as local intent metadata.
This module exists in anticipation of automated app generation and long-term headless page management.
It must persist:
- page definitions
- page sections
- composition order
- page-level content bindings
- route-level intent
- page design primitives
- brochure-page purposes
- deck-page purposes
- theme and decoration tokens as page-scoped design intent
- layout-specific metadata
- SEO and page metadata
The Page object is expected to cover a broad family of page-layout intent objects that is still evolving.
That evolving scope is one more reason it must be its own BFF module.
Site BFF Intent¶
The Site BFF is the persistence target for higher-order site objects that organize and govern sets of pages.
The right analogy is a book:
- a
Sitecontains several pages - a
Sitehas book-like metadata - a
Sitemay have cover-like structures - a
Sitemay have index and table-of-contents style structures - a
Siteowns navigation and cross-page composition concerns
It must persist:
- site identity
- site title and descriptive metadata
- cover-like metadata
- route structure across pages
- navigation
- index and table-of-contents style structures
- site-scoped theme tokens
- site-level SEO and metadata
- SEO and metadata
It must not be forced into the same object surface as Product, Article, or Page.
Anti-Contamination Rules¶
- Product module fields must not be renamed to fit article or site conventions.
- Article module author/publishing semantics must not be constrained by product persistence shortcuts.
- Page module composition semantics must not be derived from current generated-app file shapes.
- Site module composition semantics must not be collapsed into page-level persistence only.
- Provider limitations must not redefine public contracts.
- Cross-module references must use explicit bindings, not accidental field reuse.
Data Source Flexibility Rule¶
Each module must be able to change provider strategy without breaking the public contract.
This means:
- the public SDL is provider-agnostic
- adapter layers absorb source differences
- storage shape is not public contract
- one module may move faster than another without forcing contract drift across the others
Required Outcome¶
From now on, dirac-fed must apply general modular BFF design discipline to
the marketing domain as the current worked example, and extend the same
discipline to other domains as they are brought into FED.
The minimum target architecture is:
Contact BFFProduct BFFArticle BFFPage BFFSite BFF
The Product BFF must adopt one canonical Product object model and retire the false Product versus Item ontological split.
That requirement is not a suggestion. It is the corrected domain intent.