Skip to content

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 BFF
  • Product BFF
  • Article BFF
  • Page BFF
  • Site 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 Facebook or Google sign-in
  • lightweight contact update and suppression flows

Primary public objects:

  • Contact
  • Consent
  • ExternalIdentity

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:

  • Product
  • ProductCollection
  • ProductMediaAsset
  • PropertyDefinition
  • PropertyValue
  • BomLine

3. Article BFF

Primary use:

  • article publishing
  • article retrieval
  • article collections
  • blog feeds
  • social posting preparation and distribution metadata

Primary public objects:

  • Article
  • ArticleCollection
  • ArticleMediaAsset
  • AuthorProfile or 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:

  • Page
  • PageSection
  • RouteDefinition
  • ContentBinding
  • LayoutPrimitive
  • PageIntentProfile

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:

  • Site
  • SiteCover
  • NavigationNode
  • ThemeTokenSet
  • SiteMetadata
  • TocStructure

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
  • Product
  • ProductCollection
  • Article
  • ArticleCollection
  • 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, and Article BFF are 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 Contact must target the Odoo res.partner model

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.partner as the primary system-of-record object

This is different from:

  • Article BFF
  • Page BFF
  • Site 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:

  • contactKey
  • contactType
  • displayName
  • firstName
  • lastName
  • email
  • phone
  • companyName
  • jobTitle
  • countryCode
  • locale
  • status
  • emailVerified
  • phoneVerified
  • acquisitionSource
  • sourceCampaign
  • sourceChannel
  • externalIdentity
  • tags
  • consents
  • audit

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:

  • contactKey is 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:

  • acquisitionSource
  • sourceCampaign
  • sourceChannel

Suggested source vocabulary should start with values such as:

  • WEBSITE_FORM
  • NEWSLETTER_FORM
  • FACEBOOK_LOGIN
  • GOOGLE_LOGIN
  • META_LEAD_AD
  • MANUAL_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:

  • provider
  • providerSubject
  • providerEmail

This supports:

  • Facebook login
  • Google login
  • third-party lead-form identity reconciliation

Consent must be explicit contract data, not only inferred from tags.

At minimum, each consent line should support:

  • consentType
  • granted
  • grantedAt
  • captureMethod

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:

  • lead
  • newsletter
  • facebook-lead
  • self-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:

  • Contact resolver entry must be res.partner
  • provider adapters must map from res.partner into the canonical Contact object
  • do not invent a separate provider-side contact object unless later design work explicitly changes this contract

Likely mapping direction:

  • name -> displayName
  • email -> email
  • phone / mobile -> normalized phone
  • company_type -> helps derive contactType
  • 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
  • email
  • phone optional
  • country optional
  • consent checkboxes

Expected behavior:

  • create or upsert a Contact
  • set status = REGISTERED or LEAD by policy
  • set acquisitionSource = WEBSITE_FORM
  • apply tags like self-registered
  • store consent events

Newsletter Signup

Typical input:

  • email
  • 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 Facebook
  • Continue with Google

Expected behavior:

  • resolve or create contact using provider identity
  • populate externalIdentity
  • use provider email if present
  • set acquisition source such as FACEBOOK_LOGIN or GOOGLE_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:

  • Node
  • Taggable
  • Auditable
  • ConsentBearing

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:

  • contactType
  • firstName
  • lastName
  • email
  • phone
  • companyName
  • jobTitle
  • countryCode
  • locale
  • tags
  • consents
  • sourceCampaign
  • sourceChannel
  • status
  • 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:

  • assignContactToServicedAccount
  • setContactActorAssignment
  • moveContactToServicedAccount

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:

  • Product was treated as a first-class commercial object
  • Item was treated as a lesser material object

That is incorrect for this domain.

The correct model is:

  • there is one canonical Product object 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:

  • productKey
  • slug
  • name
  • subtitle
  • shortDescription
  • description
  • mainMedia
  • mediaGallery
  • publishState
  • productClass
  • isComponent
  • canAppearStandalone
  • canBeIncludedInBom
  • bop
  • bom
  • collections
  • tags
  • seo
  • audit

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:

  • productKey is the canonical persistence identity
  • slug is the public route identity

The default slug rule follows the repository-wide headless content policy:

  1. derive from product title or name
  2. lowercase it
  3. remove punctuation and special characters
  4. normalize remaining text into hyphenated words
  5. truncate the readable stem as needed
  6. suffix with a 6-character digest derived from productKey
  7. 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 Product is 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 Product object is bound to an SKU
  • that SKU linkage is the bridge to the Odoo product substrate

A useful conceptual helper is:

  • think of Product as 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 Product object into raw Odoo shape

So the current intent is:

  • public Product remains 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 PropertyDefinition defines semantic meaning
  • a PropertyValue binds 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 item object 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:

  • Node for stable identity
  • Publishable
  • Describable
  • MediaBearing
  • PropertyBearing
  • ComposableProduct

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:

  • articleKey
  • slug
  • title
  • subtitle
  • body
  • heroImage
  • author
  • publishState
  • publishedAt
  • updatedAt
  • collections
  • tags
  • seo
  • audit

Article Identity and Slug Rule

For Article:

  • articleKey is the canonical persistence identity
  • slug is the public route identity

The default slug rule follows the repository-wide headless content policy:

  1. derive from article title
  2. lowercase it
  3. remove punctuation and special characters
  4. normalize remaining text into hyphenated words
  5. truncate the readable stem as needed
  6. suffix with a 6-character digest derived from articleKey
  7. 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:

  • author is a single public string
  • that string is both the public byline and the lookup key for future external profile enrichment
  • author.role is not part of the current Article BFF persistence contract
  • author.role is 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 author string 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:

  • body is 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:

  • collectionKey
  • slug
  • name
  • description
  • media
  • articles
  • articlesCount
  • publishState
  • seo
  • audit

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:

  • title
  • subtitle
  • body
  • heroImage
  • author
  • tags
  • collections
  • publishState

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:

  • Node
  • Publishable
  • Describable
  • MediaBearing
  • Taggable
  • Collectable

These are interface aids only.

The canonical business objects remain:

  • Article
  • ArticleCollection

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 Site contains several pages
  • a Site has book-like metadata
  • a Site may have cover-like structures
  • a Site may have index and table-of-contents style structures
  • a Site owns 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

  1. Product module fields must not be renamed to fit article or site conventions.
  2. Article module author/publishing semantics must not be constrained by product persistence shortcuts.
  3. Page module composition semantics must not be derived from current generated-app file shapes.
  4. Site module composition semantics must not be collapsed into page-level persistence only.
  5. Provider limitations must not redefine public contracts.
  6. 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 BFF
  • Product BFF
  • Article BFF
  • Page BFF
  • Site 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.