Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Keep the UI Dumb: Let Domain Experts Publish Schemas

    Hard-coding product rules in the front end is often the fastest route to production, and a trade-off whose debt compounds as products multiply. Business rules belong to the domain that owns them: when domain experts publish schemas, the UI becomes a framework that renders any product.

    Seedtended planted 8 min read

    🌱 A rough idea, planted recently. Expect rough edges — it will grow.

    On this page

    There’s a pattern I’ve now built twice, in two very different industries, and it comes down to one rule: the UI shouldn’t know why a field exists.

    The problem: business rules leaking into the front end

    Picture a trading platform with a derivatives pricer. Every product has its own shape: which fields matter, which ones appear only for certain instruments, which combinations are valid, how the legs of a structure relate to each other. The people who really understand that are the quants.

    The usual way it goes: the quants design a new product, explain it to the UI team, and the UI team implements it. That means conditionals: if this instrument, show these fields; if that structure, hide those; validate this range only when that flag is set. Product by product, the front end fills up with business-critical logic it has no business owning.

    It causes three problems at once:

    • The wrong people own the rules. A pricing rule written by a front-end engineer from a conversation is a translation of a translation. When it’s wrong, nobody notices until a client does.
    • The UI team becomes the bottleneck. The quants have a backlog of products; the UI team has a backlog of everything else. Every new product waits for UI capacity.
    • Drift. Each product is hand-built slightly differently, and the front end and back end slowly disagree about what a product is.

    Why hard-coding wins at first

    To be fair to the hard-coded version: it’s usually the fastest way to production. With two or three simple products, a few conditionals in the front end is genuinely quicker than designing a schema language, and a proof of concept should be quick. I’ve made that call myself, and it was the right one at the time.

    But it’s a trade-off, and a big one. You’re borrowing against the future, and the debt compounds:

    • It starts as easy proof-of-concept logic for a handful of simple products.
    • Then the products multiply: a dozen, then several dozen, each with its own quirks and exceptions.
    • The logic that was “just a few ifs” becomes deep branching: special cases inside special cases, copied between screens, with no single place that says what a product is.
    • Every new product costs more than the last, because it has to fit around everything before it.

    The mistake isn’t hard-coding the first products. It’s not noticing when the trade-off has flipped, and carrying on paying the debt forward long after a framework would have been cheaper. My rough signal: when the third product arrives and you’re copying the second one’s branches, or when the domain team is regularly waiting on the UI team, it’s time to move the knowledge out of the front end.

    Even before a full schema-driven framework, there’s a cheaper middle step: well-typed interfaces. Model each product as an explicit type (a discriminated union, one variant per product shape) instead of loose objects checked with ifs. The compiler then tells you every place a new product needs handling, and the shape of each product is written down in one place. It’s often the bridge that makes the later move to schemas straightforward.

    The fix: domain experts publish schemas

    Flip it around. The domain team, here the quants, publishes a schema for each product: its fields, their types, which ones are visible for which instruments, how they group, how they’re validated, how they render. The UI becomes a framework that can render any product schema. It knows how to draw a number input, a date, a grouped leg, a tooltip. It doesn’t know what an accumulator is.

    In domain-driven design terms:

    • The pricing context owns the shape and the rules of a product. That’s its language.
    • The UI context owns presentation: layout, interaction, accessibility.
    • The schema is the published language between them: a contract the domain team writes and the UI consumes (the same idea as in Contract-Driven Development, and the seam I describe in Planes and Bounded Contexts).

    The rules themselves execute on the back end, in the domain that owns them. The UI can mirror validation so the form feels instant, but it never owns a rule.

    The UI is rarely the source of truth

    Underneath all of this is a broader principle: the UI should almost never be the source of truth. It’s a view of state that lives somewhere else: a projection, kept in sync, that people can interact with. When the truth lives in the UI, three things go wrong:

    • Rules get duplicated and drift. A rule enforced only in the browser can be bypassed by anyone calling the API directly, and the next client (a mobile app, another service, an AI agent) has to reimplement it and will get it slightly wrong.
    • There’s no single answer to “what is this?”. If a product’s shape lives in front-end conditionals, the back end, the reports and the audit trail all have their own version.
    • Money and risk end up calculated in the least trustworthy place. Prices, totals and limits belong on the server, as I argue in Let the Server Do the Money Maths.

    Schema-driven UI follows naturally. If the domain owns the truth about what a product is, the domain should publish it, and the UI should render it. The same shape shows up in real-time screens: the server owns the state and streams it, and the client holds a projection it reconciles (Snapshot and Delta Sync over WebSockets). The UI can still validate instantly or update optimistically for a snappy experience, but the server confirms, and the server wins.

    What the UI genuinely does own is its own state: which panel is open, a draft the user hasn’t submitted yet, the theme, scroll position. That’s presentation, and it’s rightly the UI’s. Business truth isn’t.

    What changes in practice

    • Launching a product is a schema change, not a UI project. The quants add the product; the framework renders it.
    • The same framework gets reused. Once a schema-driven form exists, other teams can use it too. On the same platform, the execution developers started defining new trading strategies the same way: their own fields and rules, rendered by the framework, without UI work.
    • Feedback improves every product at once. When clients asked for clearer forms, adding grouping, tooltips and guidance to the framework improved every product, not just one.
    • The front end shrinks. Less conditional logic, fewer special cases, and the code that’s left is genuinely about presentation.

    I first built this shape on a low-code network automation platform, where JSON and Jinja schemas let network engineers define workflows and device configuration without waiting for developers. The domain was completely different; the rule was the same: let the people who understand the domain define it, and build the framework that renders it.

    “But presentation is UI work!”

    The most common pushback I hear: schemas put presentational logic on the back-end or domain team, and presentation is the UI’s job. It’s a fair objection, and the answer is that a good domain schema doesn’t describe presentation at all. Split it in two:

    Domain schema (owned by the domain team)UI schema / renderer (owned by the UI team)
    Sayswhat a product is: fields, types, units, constraints, which fields apply to which instruments, how fields relatehow it looks: widgets, layout, ordering on screen, responsiveness, accessibility, styling
    Examplestrike: number, required, > 0, applies to: options“numbers with units render as a stepper; option legs render in a table”
    Changes whenthe business changesthe design changes

    The domain team describes meaning; the UI team decides presentation. The UI maps each field type to a widget through its own registry, and when a product needs special treatment, the UI team adds a small UI schema keyed by field names: “show these two side by side”, “use the custom leg editor here”. That’s the same split as JSON Schema plus a separate uiSchema in common form libraries.

    Some things sit on the boundary, like tooltips and guidance text. What a field means is domain knowledge, so the experts write it; where and how it appears is the UI’s call.

    Groups are the clearest example. When the domain schema says “these fields form the pricing inputs” and “these form the risk limits”, it’s not doing layout. It’s telling the UI which fields belong together as a meaningful block of work, context the UI team often couldn’t work out on its own. That’s domain knowledge, and it’s the bridge between the two sides. What the UI then does with each group is entirely its own domain: the separation between them, whitespace, colour, whether a group is a card, a tab or a collapsible section, and how it all reflows on a phone. The domain says what belongs together; the UI decides how that looks.

    Ordering follows the same split. By default, the UI can simply render groups and fields in the order they appear in the schema: the domain team’s natural reading order, and a sensible starting point. If the UI team wants a different order for a particular screen, that’s a presentation decision, made in its own UI schema.

    The data structure itself can carry that intent:

    • An array means “order matters”. The domain team is saying “read these in this sequence”, and the UI follows it by default.
    • An object keyed by name means “no order implied”. It’s a set of groups or fields, and placing them is entirely the UI’s decision.

    That’s not just a convention. JSON doesn’t guarantee object key order, and JavaScript reorders keys that look like numbers, so an object can’t reliably carry order anyway. If the order matters, an array is the only way to say so.

    So the UI team doesn’t lose presentation; it stops owning business rules it was never the expert on, and gets to spend its time on the framework, the components and the experience, which is the work it actually wants to do.

    The trade-offs

    • A schema language is a product. It needs versioning, documentation and a preview, or the domain experts can’t actually use it on their own. Treat them as its users.
    • Keep an escape hatch, and keep it rare. Some products genuinely need a bespoke widget. Allow custom components, but make them the exception, or you’re back to hand-building everything.
    • Don’t build a programming language in JSON. If the schema needs loops and arbitrary conditionals, the logic is trying to leak back into the client. Keep the vocabulary small and shaped like the domain.

    The bigger idea

    It’s very easy, with the best intentions, to build a product end to end yourself. But for a product to make sense end to end, each end needs its own expert’s guidance. A platform’s job isn’t to know everything. It’s to let the domain experts express what they know, safely and directly, and turn that into something everyone else can use. That’s the same reasoning behind Where Platforms Fit: DDD Subdomains and Ubiquitous Language for Platforms: the platform speaks the domain’s language, and the domain team owns it.

    • #domain-driven-design
    • #bounded-contexts
    • #schema-driven-ui
    • #json-schema
    • #platform-engineering
    • #frameworks
    • #published-language
    • #source-of-truth
    • #tech-debt
    • #trade-offs
    • #typescript

    Nothing links here yet — it’s a fresh shoot.

    ↪ Links to (6)