A3.6 · The program roadmap · dive 2
One core, many surfaces
The Portal is one domain core behind one facade, surfaced through several adapters — a web app, a bot, a future worker, each an adapter over the same facade.
The web (F6) and the bot (F10) are parallel surfaces over that one Portal
facade. The master invariant is what makes them independent: every surface calls only the facade and renders
only the closed error set. Pick a surface on the right to map it to its adapter, all routed through the one
facade.
The core and its surfaces · pick one
One core, surfaced through adapters
The Portal has one domain core, framework-free, behind a single facade. From
portal.roadmap.md, verbatim: “There is one domain core, framework-free, behind
a single facade (Portal); every surface — a LiveView, a bot handler, a future
worker — calls only that facade and renders only the closed %Portal.Error{} set.
The program grows by adding surfaces and capabilities over the unchanged core, never by reaching into it.”
A surface is an adapter. The web app (F6) is a LiveView/HEEx adapter; the bot (F10) is an ex_gram handler; a future worker is reserved. Each is its own chapter with its own roadmap, and each sits on the same facade. From the same file: “F6 (web) and F10 (bot) are parallel surfaces over the same F5 facade — neither depends on the other for its core loop.”
The master invariant, verbatim: “The web layer calls only the
Portal facade and renders only the closed %Portal.Error{} set.
No controller, LiveView, plug, or template names Portal.Engine, a repo, or
GenServer.call.” That single rule is what lets a surface be added over the
unchanged core.
Add or remove a surface — the core is unchanged
Because every surface calls only the facade, a surface can be added or removed without touching the core. The figure below toggles each surface on or off. However many surfaces are present, the “core changes” counter holds at zero — adding the bot does not change the engine; removing the web app does not change the engine. The master invariant holds throughout.
The two figures teach different moves: the hero maps one surface to its adapter over the shared facade; this one toggles surfaces present or absent and proves the core never changes.
Toggle a surface on or off · the core does not change
A surface is an adapter over the core — add or remove one and the framework-free core does not change.
One core, many surfaces, on the Portal
On the Portal, the core is the F5 engine behind the Portal facade. The surfaces are
the web chapter F6 and the bot chapter F10 (with a worker reserved for F7–F9). Each surface is a roadmap
of its own that calls only the facade. The companion course
F6 · The Portal on the web (Phoenix) is where the framework
implements the web surface; this layer only sequences the chapters.
The hand-off is clean precisely because of the master invariant. A new surface adds a roadmap and renders only the closed error set; it invents no domain logic and no new error vocabulary, so the core stays unchanged.
# portal.roadmap.md — how the chapters compose (a sketch; verbatim relations). # F5 the engine ──▶ Portal facade + closed %Portal.Error{} # ├──────────────┬───────────────────────────┐ # ▼ ▼ ▼ # F6 the web F10 the bot F7–F9 multi-runtime (open) # (LiveView) (ex_gram) EchoMQ · workers # Each surface is an adapter over the unchanged core. core = "the F5 engine" # framework-free, behind one facade surface = "F6 web | F10 bot" # adapters; each a roadmap of its own rule = "call only the facade" # the master invariant; the decoupling seam # Add a surface, add a roadmap; the core never changes.
One core, many surfaces — a surface is an adapter over the core, added without reaching into it; the core stays the single, framework-free source of behaviour.
“Call only the facade” — the master invariant. The web (F6) and bot (F10) each call only
the Portal facade and render only the closed error set, so each is independent of the
others and of the core's internals.
The master invariant is the decoupling seam — add a surface, add a roadmap; the core never changes.
Recap — surfaces over one core
The Portal is one domain core behind one facade, surfaced through several adapters. The web (F6) and the bot (F10) are parallel surfaces; a future worker is reserved. Each calls only the facade and renders only the closed error set, so a surface is added or removed without changing the core. The next dive looks at the consequence for delivery: because the surfaces are independent, they ship on independent cadences.
Next in A3.6: A3.6.3 · Independent cadence — each surface ships on its own schedule, sequenced by product priority, not a hard dependency.
References
The decoupling sources behind one-core-many-surfaces, and where it connects in the course.
Sources
- Hunt & Thomas — The Pragmatic Programmer — orthogonality and decoupling: a seam that lets one part change without disturbing another.
- Humble & Farley — Continuous Delivery — components released independently behind a stable interface.
- Beck, K. — Extreme Programming Explained — simple design and incremental change over a stable core.
Related in this course
- A3.6 · The program roadmap — the module hub.
- A3.6.1 · Roadmap of roadmaps — the previous dive.
- A3.6.3 · Independent cadence — the next dive.
- A1.04 · Two layers — the roadmap over the spec.
- F6 · The Portal on the web (Phoenix) — where the framework implements the web surface.