|
| 1 | +# 9. Separate configurable document forms and presenters from attribute schema |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Accepted |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +The configurable document schema introduced in [ADR-0006](./0006-config-driven-content-types.md) and evolved in [ADR-0007](./0007-use-rails-validation-for-configurable-documents.md) and [ADR-0008](./0008-drop-json-schema-for-document-configuration.md) defined document properties under `schema.properties` and grouped UI fields via `settings.edit_screens`. This conflated three concerns: |
| 10 | + |
| 11 | +- How publishers see and group fields in the UI (form layout and copy). |
| 12 | +- How field values are presented to downstream systems (e.g. Publishing API payload mapping and transformations). |
| 13 | +- How attributes and validations are defined and enforced. |
| 14 | + |
| 15 | +This coupling made it hard to: |
| 16 | + |
| 17 | +- Group related fields in the UI without affecting payload mapping. |
| 18 | +- Present the same attribute differently to publishers and Publishing API. E.g Duration block in topical events holding data for `start_date` and `end_date` but needed to be presented as individual fields to publishing API. |
| 19 | +- Flatten validations while still allowing nested form structures, leading to complex validation logic. |
| 20 | + |
| 21 | +## Decision |
| 22 | + |
| 23 | +We refactored the configurable document type schema to separate concerns: |
| 24 | + |
| 25 | +1. Introduced a top-level `forms` hash describing UI forms and their fields (label, description, block type), replacing `settings.edit_screens`. |
| 26 | +2. Replaced `schema.properties` with `schema.attributes`, limited to leaf attributes; we no longer model nested properties at the schema level. |
| 27 | +3. Introduced a top-level `presenters` hash to describe how attribute values are mapped or transformed for downstream consumers (e.g. Publishing API), helping to decouple UI layout from payload shape. |
| 28 | +4. Flattened validation definitions so `schema.validations` lists validators by attribute name; nested validations inside a property definition are removed. |
| 29 | +5. Removed the option to declare attributes as `object` types to enforce the flattened model and prevent reintroduction of nested schema structures. |
| 30 | +6. Updated the configurable document type JSON schema to validate the new structure and removed obsolete nested-schema code paths. |
| 31 | + |
| 32 | +## Consequences |
| 33 | + |
| 34 | +- All configurable document type definitions must use `schema.attributes`, `forms`, and `presenters`; `settings.edit_screens` and nested property validations are no longer supported. |
| 35 | +- UI layout changes (field grouping, titles, descriptions, block types) can now be made in `forms` without impacting Publishing API payloads, which are defined in `presenters`. |
| 36 | +- Validators will run against the flattened `schema.attributes` namespace. Added tests ensure nested data values stored in block content are preserved when present in the payload. |
| 37 | +- Future support for genuinely nested attribute schemas or arrays would need a new design rather than reintroducing `object` types. |
| 38 | +- It now becomes easier to present the same attribute differently in the UI and Publishing API (or any other consumer in future) by defining separate mappings in `forms` and `presenters`. |
| 39 | + |
| 40 | +## Example |
| 41 | + |
| 42 | +A configurable document type schema following the new structure will be identical to this: |
| 43 | + |
| 44 | +```json |
| 45 | +{ |
| 46 | + "key": "example_document_type", |
| 47 | + "title": "Example Document Type", |
| 48 | + "description": "An example document type with configurable forms and presenters", |
| 49 | + "forms": { |
| 50 | + "documents": { |
| 51 | + "fields": { |
| 52 | + "body": { |
| 53 | + "title": "Body", |
| 54 | + "description": "The main content area", |
| 55 | + "block": "govspeak" |
| 56 | + }, |
| 57 | + "lead_paragraph": { |
| 58 | + "title": "Lead Paragraph", |
| 59 | + "description": "A short introduction", |
| 60 | + "block": "default_string" |
| 61 | + } |
| 62 | + } |
| 63 | + } |
| 64 | + }, |
| 65 | + "schema": { |
| 66 | + "attributes": { |
| 67 | + "body": { |
| 68 | + "type": "string" |
| 69 | + }, |
| 70 | + "lead_paragraph": { |
| 71 | + "type": "string" |
| 72 | + }, |
| 73 | + }, |
| 74 | + "validations": { |
| 75 | + "presence": { |
| 76 | + "attributes": ["body", "lead_paragraph"] |
| 77 | + }, |
| 78 | + "length": { |
| 79 | + "attributes": ["lead_paragraph"], |
| 80 | + "maximum": 255 |
| 81 | + } |
| 82 | + } |
| 83 | + }, |
| 84 | + "presenters": { |
| 85 | + "publishing_api": { |
| 86 | + "body": "govspeak", |
| 87 | + "lead_paragraph": "string" |
| 88 | + } |
| 89 | + } |
| 90 | +} |
| 91 | +``` |
0 commit comments