Fabric Medallion Architecture for Warehouse Governance

Fabric medallion architecture in practice: what the new warehouse guidance changes

Fabric Medallion Architecture for Warehouse Governance

Bronze, silver, and gold won’t save your Fabric rollout. The teams that win with Microsoft Fabric treat medallion as an operating model with hard rules, not a pretty three-box diagram.

On this page

A better visual for this idea isn’t a generic capability map. It’s a side-by-side contrast: the simple three-layer medallion on one side, and on the other a real operating model with contracts, quality gates, ownership, and serving boundaries.

Microsoft recommends medallion for Fabric, and that matters. But too many teams read it as a naming convention instead of a platform contract. Fabric is a unified analytics platform with OneLake underneath it all, and medallion is the recommended design pattern in that world.

That’s the starting point. It is not the implementation.

If you stand up ten domain workspaces and tell every team to create bronze, silver, and gold, you have not created architecture. You have created ten different interpretations of quality, ownership, access, and cost. That’s how a Fabric deployment looks clean in the pilot and messy by quarter two.

The medallion diagram is not a governance model

Here’s the blunt version: a layer name does not establish accountability.

“Bronze” does not mean raw is preserved correctly. “Silver” does not mean the data is trusted. “Gold” does not mean the table is safe for self-service BI.

Those words only become useful when the platform team makes them enforceable.

Microsoft’s guidance is clear on the pattern: bronze holds raw data, silver standardizes it, and gold supports consumption. Fine. But the real architecture decisions still sit with you:

  • who owns promotion
  • what evidence is required
  • where dimensional models live
  • which consumers can hit which layer
  • who pays when duplication spreads across the estate

A retail customer I worked with had 14 Fabric workspaces, 6 data teams, and three different definitions of “gold” before the first executive KPI review blew up because finance and merchandising were reading different margin logic from different promoted tables.

That’s not a tooling failure. That’s an operating model failure.

Turn the layers into enforceable contracts

The way I implement medallion in Fabric is simple: every layer has a contract, and every contract answers five questions.

  1. What inputs are allowed?
  2. Who is accountable?
  3. What quality evidence is required?
  4. How long is it retained?
  5. Who is allowed to consume it?

If you cannot answer those five questions for a dataset, it has no business being promoted.

Here’s the reference pattern I like for a warehouse-oriented Fabric deployment:

  • Bronze = governed landing boundary
  • Silver = standardization and validation boundary
  • Gold = curated consumption boundary
  • Semantic model = business-facing serving layer

That distinction matters. Fabric supports multiple analytical experiences on one platform, but consumers still need clean boundaries between source-faithful data and governed business data.

I’d sketch the flow like this:

Diagram 1

What matters in that diagram is the handoff point: gold is served from the warehouse, and the semantic layer sits on top with intent. That is very different from “we have some silver tables, let’s point Power BI at them and hope for the best.”

For bronze-to-silver, I want explicit cleanup, schema handling, deduplication, and timestamp normalization. Not one-off notebook logic hidden in somebody’s personal workspace.

# Bronze to Silver cleanup in Fabric notebook with Delta tables
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, to_timestamp, trim

spark = SparkSession.builder.getOrCreate()

bronze = spark.read.format("delta").load("Tables/bronze_sales")
silver = (
    bronze
    .withColumn("order_ts", to_timestamp(col("order_time")))
    .withColumn("customer_id", trim(col("customer_id")))
    .filter(col("order_id").isNotNull())
    .dropDuplicates(["order_id"])
)

silver.write.mode("overwrite").format("delta").save("Tables/silver_sales")
print("Silver table refreshed from Bronze.")

The point is not the code itself. The point is the promotion rule. Silver should exist because the data passed a standard, not because someone needed a dashboard by Friday.

Standardize domain boundaries before you create more workspaces

A domain is not a folder. It is an accountable product boundary.

Every domain in Fabric should have:

  • a named business owner
  • a named technical owner
  • source onboarding rules
  • layer contracts
  • approved consumer classes
  • an exception path when source quality breaks

Without that, your “shared” architecture becomes a political architecture. Bronze turns into an enterprise dumping ground. Silver turns into a global integration swamp. Gold turns into whatever the loudest reporting team asked for last week.

This is where platform teams usually get lazy. They standardize naming, workspace templates, and CI/CD. All useful. None of that solves ownership.

The first thing I ask in an architecture review is: who has the authority to reject promotion from bronze to silver? If nobody owns that decision, your medallion pattern is decorative.

Cross-domain integration needs the same discipline. If sales needs customer conformance from finance and product conformance from merchandising, decide where that integration belongs. Don’t accidentally create one giant shared silver area that one central team now owns forever.

Quality gates are the real middle layer

The most important design choice in medallion is not the number of layers. It is the promotion rule between them.

If I’m reviewing a Fabric warehouse pattern, I want to see promotion evidence for every silver and gold asset:

  • source lineage
  • schema expectation
  • freshness expectation
  • validation outcomes
  • exception owner
  • intended consumers

That creates a clean separation between raw preservation and trusted publication.

Bronze should preserve source truth, including ugly truth. Late files, null-heavy payloads, malformed records, source drift, all of it. Silver is where you standardize and decide whether the data is fit for broader use. Gold is where you publish analytics-ready products with clear semantics.

When teams skip this, they quietly weaken quality thresholds. A source arrives late, so they mark stale data as current. A column changes type, so they coerce it and move on. A key goes missing, so duplicates bleed into reporting.

I would rather fail promotion loudly than publish garbage politely.

Keep dimensional models separate from medallion layers

This is the mistake I see most often in Fabric warehouse rollouts: teams assume “gold” automatically means star schema, semantic model, and BI-ready product.

No. Gold is a consumption boundary. It is not a substitute for dimensional design.

Microsoft’s Power BI guidance is still the right north star here: analytics models should generally follow star-schema principles, and that guidance points readers toward dimensional modeling in Fabric Warehouse. That should tell you something important. Dimensional modeling is its own design decision.

So I set a platform rule:

  • medallion layers govern data progression
  • warehouse models govern analytical serving
  • semantic models govern business consumption

Different lifecycle. Different approval. Different owner if needed.

If I’m building a sales analytics product, I might standardize orders and products in silver, aggregate and conform metrics into a gold-ready structure, then load a proper fact table and dimensions into the warehouse for governed SQL serving.

# Build a Gold-ready star schema table before loading the Warehouse
from pyspark.sql import SparkSession
from pyspark.sql.functions import sum as sum_, col

spark = SparkSession.builder.getOrCreate()

sales = spark.read.format("delta").load("Tables/silver_sales")
products = spark.read.format("delta").load("Tables/silver_products")

fact_sales = (
    sales.join(products, "product_id", "left")
    .groupBy("order_date", "product_id", "category")
    .agg(sum_("amount").alias("sales_amount"), sum_("quantity").alias("units"))
    .select("order_date", "product_id", "category", "units", "sales_amount")
)

fact_sales.write.mode("overwrite").format("delta").save("Tables/gold_fact_sales")
print("Gold fact table prepared in the Lakehouse.")

What to look for here: the transformation is shaping analytical grain and conformance, not just moving data forward because the next box in the diagram says “gold.”

Then I validate that the serving target is intentional:

# Validate that Gold tables follow Warehouse-first serving guidance
$tables = @(
    @{ Name = "dim_customer"; Layer = "Gold"; Target = "Warehouse" }
    @{ Name = "dim_product"; Layer = "Gold"; Target = "Warehouse" }
    @{ Name = "fact_sales"; Layer = "Gold"; Target = "Warehouse" }
)

$tables |
    Where-Object { $_.Layer -eq "Gold" -and $_.Target -eq "Warehouse" } |
    ForEach-Object { "OK: {0} is served from {1}" -f $_.Name, $_.Target }

If your “gold” tables are still scattered across ad hoc lakehouse outputs with no serving standard, you don’t have a warehouse pattern. You have a naming pattern.

Choose operability over convenience

Fabric gives you options. Good. Options are useful. They also let teams rationalize bad architecture.

A shortcut is an access decision. It is not a governance decision. Making data visible faster does not make it governed.

A warehouse pattern is a serving decision. Use it when you need dimensional models, governed SQL access, stable analytical structures, and predictable consumption. A transformation pipeline is an operational decision. Use it when you need repeatability, observability, and supportable promotion across layers.

If you want a simple way to explain the old habit versus the stronger warehouse-oriented pattern, I use this comparison:

# Compare old all-Lakehouse serving with new Warehouse-serving guidance
guidance = {
    "old_pattern": ["bronze_lakehouse", "silver_lakehouse", "gold_lakehouse", "bi_direct"],
    "new_pattern": ["bronze_lakehouse", "silver_lakehouse", "gold_warehouse", "semantic_model"]
}

for name, steps in guidance.items():
    print(f"{name}:")
    for i, step in enumerate(steps, start=1):
        print(f"  {i}. {step}")

The thing to observe is the last mile. “gold_lakehouse -> bi_direct” is where a lot of pilots look productive and later become expensive. “gold_warehouse -> semantic_model” is slower on day one and far more survivable at scale.

Here’s a lightweight deployment shape I like teams to document before they build:

# Create a Warehouse-focused deployment config for medallion promotion
$deployment = @{
    Workspace = "Fabric-Prod"
    LakehouseBronze = "lh-bronze"
    LakehouseSilver = "lh-silver"
    WarehouseGold = "wh-gold"
    SemanticModel = "sales-model"
    Strategy = "Transform in Lakehouse, serve in Warehouse"
}

$deployment.GetEnumerator() |
    Sort-Object Name |
    ForEach-Object { "{0}={1}" -f $_.Name, $_.Value }

If a team cannot state its strategy in one screen, it usually has not made the hard choices yet.

Failure modes that look like fast delivery

Bad medallion implementations often look successful at first:

  • Bronze becomes a reporting source because nobody defined trusted promotion.
  • Silver becomes a shared enterprise integration layer with no domain owner.
  • Gold becomes a junk drawer of extracts, marts, and one-off reporting tables.
  • Shortcuts get mistaken for governed products because they work quickly.
  • Semantic models rebuild inconsistent measures because warehouse and BI ownership were never separated.

Scale exposes the architecture you actually built, not the one in the slide deck.

The practical standard I’d adopt now

If you’re rolling out Fabric across domains, keep the standard small enough to enforce.

For each domain:

  • identify business and technical owners
  • define source onboarding rules
  • define bronze, silver, and gold contracts
  • define approved consumer types by layer

For each promoted dataset:

  • capture lineage
  • record freshness target
  • record validation results
  • assign an exception owner

For each warehouse product:

  • define fact and dimension ownership
  • define semantic model ownership
  • define metric approval path

For the platform:

  • publish the standard
  • reuse the pattern
  • force architecture review when a domain deviates

Microsoft has already given the recommended pattern for Fabric medallion architecture. Your job is to make it operational. If a new domain cannot demonstrate ownership, quality gates, consumption boundaries, and operational tradeoffs, it has not earned a place in the shared platform yet.

Where does this break in your environment: domain ownership, silver promotion rules, or the warehouse-versus-lakehouse serving decision?

#MicrosoftFabric #DataArchitecture #AnalyticsEngineering


Sources & References

  1. Microsoft Fabric documentation
  2. Implement Medallion Lakehouse Architecture in Fabric
  3. Lakehouse end-to-end scenario: overview and architecture
  4. Understand star schema and the importance for Power BI
  5. Dimensional modeling in Microsoft Fabric Warehouse

Try it yourself

Run this tutorial as a Jupyter notebook: Download runbook.ipynb (30 cells, 27 KB).

Link copied