Why we now design data products by Topic × Medallion (and the rule of thumb that saved our sanity)

For a long time, my mental model was simple:
“A data product is a business topic. Full stop.”
Customer. Policy. Claims. Payments. Contracts. One product per topic. Clean. Domain-driven. Easy to explain. And honestly: it works—until it doesn’t.
Then production happened. Not a demo. Not a PowerPoint architecture. Real consumers. Real dependencies. Real backfills. Real “can you just…” requests that turn into permanent features. And suddenly our orchestration graph (the DAG) started to look like a bowl of spaghetti.
That’s when we learned the hard way:
A data product cannot only be sliced by topic.
It must be sliced by topic and by medallion (maturity level).
If you don’t, you’ll run into orchestration and DAG issues that feel “mysterious” at first—but are completely predictable in hindsight.
This is the post I wish I had read before we made that mistake.
The belief: “Topic-slicing is enough”
The logic sounds convincing:
- A topic is a natural boundary aligned to a domain team.
- A topic is stable over time (unlike technical layers).
- Consumers care about business meaning, not pipeline internals.
- One product per topic feels like “true product thinking.”
So we built “topic products” that contained everything: raw ingests, cleaned tables, curated marts, KPIs. We exposed multiple outputs (what many call “output ports”) but we kept it one product, one lifecycle, one orchestration story.
The pitch was clean:
- “Here’s the Customer data product.”
- “It serves the whole org.”
- “It contains everything you need.”
And for a while, it looked like success.
The reality: the DAG doesn’t care about your clean story
The first cracks appeared in orchestration and operations—not in modeling.
1) Refresh coupling: “Why did Gold fail because Bronze had a hiccup?”
When your topic product contains multiple maturity levels, you accidentally couple them:
- Bronze ingestion fails (late file, upstream outage)
- Silver transformations can’t run
- Gold KPIs don’t refresh
- Consumers see stale dashboards and blame “the product”
Even if your consumers only care about Gold, they are now hostage to Bronze instability—because you built them into one pipeline chain.
2) Backfills become a weapon of mass disruption
Backfills are where topic-only slicing really hurts.
If you backfill Bronze for “Customer” (say you corrected a parsing bug), you’ll often trigger:
- reprocessing for Silver,
- recalculation for Gold,
- downstream product refresh storms,
- and a wave of “why did my numbers change?” messages.
What should be an internal correction turns into a cross-org event—because your “one topic product” is also “one blast radius.”
3) Mixed SLAs inside one product create permanent confusion
A single product with mixed maturity levels quietly creates mixed expectations:
- Bronze: “may be late, may be messy, but complete and traceable”
- Silver: “standardized and consistent”
- Gold: “decision-grade definitions”
But consumers don’t read your internal nuances. They hear “Customer product” and assume everything behind it behaves like the best part of it.
That leads to the worst operational state a product owner can be in:
You didn’t technically violate anything…
but people still feel betrayed.
4) The dependency graph grows in the wrong direction
A topic-only product tends to evolve like this:
- Add “just one more transformation”
- Add “just one more output”
- Add “one more consumer-specific variant”
- Add “some quick fix for that special case”
Soon, you get an internal DAG that is:
- hard to reason about,
- fragile to change,
- and expensive to run.
Not because the team is bad—because the boundary is wrong.
The root cause: you mixed two different product promises
This is the key insight:
Topic answers: What is this about?
Medallion answers: How stable/curated is it?
When you merge them into one product boundary, you mix two different product promises:
- A raw/operational promise (traceability, ingestion, replayability)
- A consumer/decision promise (semantic stability, definitions, KPI correctness)
These promises want different things:
- Different SLAs/SLOs
- Different release cycles
- Different testing strategies
- Different compute profiles
- Different change tolerance
- Different ownership conversations (yes—even if the same team builds it)
That’s why orchestration breaks: not because the pipelines are hard, but because you forced incompatible lifecycles into one product.
The fix: design by Topic × Medallion
So we changed the model:
Instead of one topic product, we built topic slices per maturity level.
- Customer–Bronze
- Customer–Silver
- Customer–Gold
(Names and exact layers can vary. The point is: separate lifecycle boundaries.)
This immediately buys you:
- Isolation of failure and variability
- Clean SLAs per slice
- Targeted backfills
- A DAG that stays readable longer
- A clearer contract story for consumers
And it makes your “output ports” meaningful: ports are no longer “everything from everywhere,” but exposure points aligned to maturity.
The decision rule: when does a medallion layer become its own product?
This is the practical question most teams ask next:
“Do I always create separate products per medallion, or can I keep some as ports?”
Here’s a rule of thumb that worked well for us:
Split into a separate product slice when at least two of these are true:
- Different reliability promise
Gold needs “never surprises”; Bronze tolerates late/raw variability. - Backfills would cause consumer impact
If backfilling raw data can change decision outputs, isolate. - Compute isolation matters
If Bronze ingestion spikes shouldn’t steal capacity from Gold reporting, isolate. - Release cadence differs
Bronze changes weekly, Gold should change monthly (or with explicit versioning). - Different consumers and access patterns
Data scientists may live in Silver; BI consumers live in Gold.
If you hit two or more, treat it as a strong signal: separate product slice.
If you hit zero or one, you can often keep it as an output port inside the same product (especially early on). But be honest: if you’re already arguing about SLAs, you’re probably past that stage.
Migration without chaos: the “strangler” approach
If you already have topic-only products, don’t “big bang” redesign.
A simple low-drama path:
- Introduce the new slices in parallel
Create Customer–Silver and Customer–Gold as first-class slices. - Dual-publish for a short window
Keep old ports for compatibility, but clearly label them as legacy. - Move consumers deliberately
Start with the consumers who hurt most from instability. - Deprecate with dates
Put an end date on old ports, enforce it, and provide migration help. - Backfill strategy changes immediately
Once slices exist, backfill within a slice boundary wherever possible.
The goal isn’t perfection. The goal is: separate the lifecycles so your operating model becomes predictable again.
What changed for me as a Data Product Owner
This shift wasn’t just architectural—it changed product ownership:
- I stopped selling “one product solves everything.”
- I started selling “a product family” with clear maturity promises.
- I gained the ability to say “yes” more often—because changes stopped being all-or-nothing.
- Reliability conversations became measurable and fair.
And most importantly:
Consumers regained trust because they could finally tell what they were getting.
Next episode: how to enforce this automatically
If you’re thinking “this is great, but how do we keep teams from wiring Gold to Bronze at 2 a.m.?”—that’s exactly episode 2.
Next, we’ll get concrete on:
- allowed output ports per product type,
- upstream subscription matrices,
- orchestration guardrails,
- and policy-as-code gates that keep the DAG clean by design.
Leave a comment