Data Mart

A Data Mart is a subject-oriented analytical data store designed to support the reporting, analytics, and decision-making needs of a specific business function, department, domain, or analytical workload.

For example:

  • Sales Data Mart
  • Finance Data Mart
  • Customer Data Mart
  • Marketing Data Mart
  • Supply Chain Data Mart
  • HR Data Mart
Enterprise Data Warehouse / Lakehouse
企业中央数据平台
          |
          |---------------- Sales Data Mart
          |                  销售数据集市
          |
          |---------------- Finance Data Mart
          |                  财务数据集市
          |
          |---------------- Customer Data Mart
          |                  客户数据集市
          |
          |---------------- Marketing Data Mart
                             市场数据集市

A Data Mart typically contains curated, integrated, business-friendly data optimized for analytical queries rather than operational transactions.

What data marts actually address

What data marts actually address is: How do we turn enterprise data into a business-consumable analytical model?

The Real Value of a Data Mart

  • Business Simplification 业务简化
  • Consistent Business Definitions 统一业务定义
  • Performance 查询性能
  • Security 安全
  • Self-Service Analytics 自助分析

Data Mart vs Data Warehouse

  • Scope and Scale / 范围与规模
    • DWH: Centralizes cross-functional data across the entire enterprise. / 汇集整个企业跨部门的全局数据。
    • DM: Isolate highly specific data for teams like sales, marketing, or finance. / 仅隔离出销售、市场或财务等特定团队所需的局部数据。
  • Structural Architecture / 架构关系
    • Top-down (Inmon): The DWH is built first as a central repository, and Data Marts are extracted from it. / 自上而下架构: 先建立中央数仓,再从中抽取并生成各个数据集市。
    • Bottom-up (Kimball): Independent Data Marts are built first for business units, then integrated to form the DWH. / 自下而上架构: 先为各业务单元建立独立的数据集市,最终组合在一起构成全局数仓。
  • Speed and Performance / 速度与性能
    • DWH: Handles massive, complex analytical queries across different business domains, which can be slower. / 处理跨领域的海量复杂分析,查询可能较慢。
    • DM: Offers much faster query responses because it scans smaller, curated datasets. / 因为只扫描较小的、精选的数据集,所以查询响应速度极快。
Feature / 特性Data Warehouse (DWH) / 数据仓库Data Mart (DM) / 数据集市
Data Scope / 数据范围Enterprise-wide, cross-functional data / 汇集全企业、跨功能数据Single subject or specific department / 单一主题或特定部门
Data Sources / 数据来源Multiple operational systems and external feeds / 多个业务系统和外部数据源Mostly derived from the central warehouse / 大多衍生自中央数仓
Size / 数据容量Typically hundreds of gigabytes to petabytes / 通常为数百 GB 到 PB 级Usually less than 100 gigabytes / 通常小于 100 GB
Implementation / 实施周期Months to years to fully deploy / 全面部署需要数月到数年Days to weeks for a targeted setup / 针对性搭建仅需数天到数周
Primary Users / 主要用户Data scientists, enterprise analysts / 数据科学家、企业级分析师Department managers, local teams / 部门经理、业务团队

The Complete Data Mart Lifecycle

Business Requirements
        |
        v
Identify Business Process
        |
        v
Identify Grain
        |
        v
Identify Dimensions
        |
        v
Identify Facts
        |
        v
Source-to-Target Mapping
        |
        v
ETL / ELT
        |
        v
Data Quality
        |
        v
Data Mart
        |
        v
Semantic Layer
        |
        v
Power BI / Reporting

Where is Data Mart in Lakehouse

              Azure
                |
       +--------+---------+
       |                  |
     CRM                ERP
       |                  |
       +--------+---------+
                |
              Bronze
                |
              Silver
                |
              Gold
                |
      +---------+---------+
      |         |         |
      v         v         v
   Sales      Finance   Customer
   Mart        Mart       Mart
      |         |         |
      +---------+---------+
                |
             Power BI

The complete implementation of a data mart

assuming, we have those in Silver:

  • silver.orders
  • silver.order_items
  • silver.customers
  • silver.products

We are creating Sales Data Mart

Step 1 — Dimension

Dim Customer

-- ============================================================
-- PURPOSE:
--   Build the Customer Dimension for the Sales Data Mart.
--
-- WHY:
--   Fact_Sales will reference customer_key instead of storing
--   all customer descriptive attributes repeatedly.
-- ============================================================

CREATE TABLE gold.dim_customer
USING DELTA
AS
SELECT
    -- --------------------------------------------------------
    -- Surrogate Key
    -- --------------------------------------------------------
    -- A surrogate key is the warehouse/Data Mart key used
    -- internally by the dimensional model.
    -- --------------------------------------------------------
    monotonically_increasing_id() AS customer_key,

    -- --------------------------------------------------------
    -- Business Key
    -- --------------------------------------------------------
    -- This is the identifier coming from the source system.
    -- Example: CRM customer ID.
    -- --------------------------------------------------------
    customer_id,

    -- --------------------------------------------------------
    -- Descriptive Attributes
    -- These attributes describe the customer.
    -- --------------------------------------------------------
    customer_name,
    city,
    province,
    customer_segment

FROM silver.customers;

Product Dimension

-- ============================================================
-- PURPOSE:
--   Create the Product Dimension.
--
-- BUSINESS PURPOSE:
--   Allow analysts to slice sales by product, category,
--   brand, etc.
-- ============================================================

CREATE TABLE gold.dim_product
USING DELTA
AS
SELECT
    -- Internal surrogate key used by the Data Mart
    monotonically_increasing_id() AS product_key,

    -- Source-system business key
    product_id,

    -- Business descriptive attributes
    product_name,
    category,
    brand

FROM silver.products;

Date Dimension

-- ============================================================
-- PURPOSE:
--   Create a reusable Date Dimension.
--
-- WHY:
--   Business users frequently analyze data by:
--       Year
--       Quarter
--       Month
--       Week
--       Day
--
--   Instead of repeatedly calculating these values from the
--   transaction timestamp, we centralize the business calendar.
-- ============================================================

CREATE TABLE gold.dim_date
USING DELTA
AS
SELECT
    date,

    -- Calendar attributes
    year(date)    AS year,
    quarter(date) AS quarter,
    month(date)   AS month,
    day(date)     AS day,

    -- Useful reporting attributes
    dayofweek(date) AS day_of_week,
    date_format(date, 'MMMM') AS month_name

FROM (
    -- Generate a calendar covering the required business period.
    SELECT explode(
        sequence(
            DATE '2020-01-01',
            DATE '2030-12-31',
            INTERVAL 1 DAY
        )
    ) AS date
);

Step 2 — Fact Sales

Fact_Sales

-- ============================================================
-- PURPOSE:
--   Build the Sales Fact Table.
--
-- GRAIN:
--   One row = one order line item.
--
-- THIS IS CRITICAL.
--   We must define the grain BEFORE loading the fact table.
-- ============================================================

CREATE TABLE gold.fact_sales
USING DELTA
AS
SELECT

    -- ========================================================
    -- Dimension Foreign Keys
    -- ========================================================

    d.date_key,

    c.customer_key,

    p.product_key,

    -- ========================================================
    -- Degenerate / Business Identifiers
    -- ========================================================

    oi.order_id,

    -- ========================================================
    -- Measures
    -- ========================================================

    oi.quantity,

    oi.sales_amount,

    oi.discount_amount,

    oi.cost_amount,

    -- Profit is derived from business measures.
    -- ========================================================
    oi.sales_amount
        - oi.discount_amount
        - oi.cost_amount AS profit_amount

FROM silver.order_items oi

-- ============================================================
-- Join Customer Dimension
-- ============================================================
JOIN gold.dim_customer c
    ON oi.customer_id = c.customer_id

-- ============================================================
-- Join Product Dimension
-- ============================================================
JOIN gold.dim_product p
    ON oi.product_id = p.product_id

-- ============================================================
-- Join Date Dimension
-- ============================================================
JOIN gold.dim_date d
    ON CAST(oi.order_date AS DATE) = d.date;
                     dim_customer
                          |
                          |
                          |
dim_date ---------- fact_sales ---------- dim_product
                          |
                          |
                       dim_store

What was revenue by customer segment and month?

SELECT
    d.year,
    d.month,
    c.customer_segment,
    SUM(f.sales_amount) AS revenue
FROM gold.fact_sales f

JOIN gold.dim_customer c
    ON f.customer_key = c.customer_key

JOIN gold.dim_date d
    ON f.date_key = d.date_key

GROUP BY
    d.year,
    d.month,
    c.customer_segment

ORDER BY
    d.year,
    d.month,
    c.customer_segment;

Key Takeaways

Data Mart: A subject-oriented analytical data store designed for a specific business domain or analytical purpose.
面向特定业务领域、部门或分析需求的数据分析存储层。

ConceptWhat is it?Main Purpose
Data LakeLarge-scale flexible data repositoryStore diverse data
Data WarehouseIntegrated analytical platformEnterprise analytics
Data MartDomain-oriented analytical storeBusiness-domain analytics
LakehouseArchitecture combining lake + warehouse capabilitiesUnified data platform

For easy remember:

Data Lake: Store Data

  • Raw
  • Flexible
  • Large scale
  • Structured + Semi-structured + Unstructured

Data Warehouse: Integrate and analyze enterprise data

  • Enterprise-wide
  • Integrated
  • Structured
  • Governed
  • Historical
  • Analytics

Data Mart: Serve a specific business domain

  • Sales
  • Finance
  • Customer
  • Risk
  • Marketing
  • HR

Lakehouse: Unify Data Lake + Data Warehouse capabilities

Scalable storage
+
ACID
+
Governance
+
SQL Analytics
+
BI
+
Data Engineering
+
ML / AI

Data Lake / Warehouse / Lakehouse describe data-platform architecture;
Data Mart describes a business-oriented analytical data product or store.

Kimball Dimensional Modeling

Data Modeling include:

  • Conceptual Modeling
  • Logical Modeling
  • Physical Modeling
  • Operational Data Modeling
  • Relational Modeling
  • Data Vault
  • Dimensional Modeling
    e.g. Kimball dimensional modeling
  • Other Modeling Approaches

Dimensional Modeling is a data modeling technique designed primarily for analytical workloads.

It organizes data around:

  • Facts — measurable business events 可以度量的业务事件
  • Dimensions — descriptive business context 描述业务上下文的信息

Kimball Dimensional Modeling

Kimball Dimensional Modeling is an approach to designing analytical data warehouses using dimensional models, centered around business processes, facts, dimensions, conformed dimensions, and a bus architecture.

Kimball Dimensional Modeling belongs to Dimmensional Modeling.

The Core Philosophy of Kimball:
Start with the business process, not the database tables.

Important concept of Kimball’s dimensional modeling

Business Process

A Business Process is a business activity that an organization performs and wants to measure, analyze, and improve.
企业正在执行的一类业务活动,而且企业希望对这类活动进行度量、分析和改进. e.g.

  • 销售 Sales
  • 订单履行 Order Fulfillment
  • 库存 Inventory
  • 支付 Payments
  • 退货 Returns
  • 保险理赔 Claims
  • 医院入院 Admissions
  • 贷款申请 Loan Applications
  • 客户服务 Customer Service

Kimball dimensional modeling starts with the business process.

Typical examples of Business Processes

Retail:

  • Sales
  • Inventory
  • Returns
  • Payments
  • Shipping

Banking:

  • Account Opening
  • Transactions
  • Payments
  • Loans
  • Credit Card Transactions
  • Fraud Detection

Government

  • Application
  • Case Processing
  • Permit Issuance
  • Payment
  • Inspection
  • Appeal

Kimball Dimensional Design — 4 Steps

The four critical steps of the Kimball lifecycle / dimensional modeling methodology are:

  1. Select the Business Process
  2. Declare the Grain
  3. Identify the Dimensions
  4. Identify the Facts

An Example: “We want to analyze credit card transactions.”

Do not create tables now.

Step 1: Select the Business Process

What is the Business Process? Credit Card Transaction

What is the Business Event? A cardholder makes a transaction
at a merchant.

Step 2: Declare the Grain

What is the Grain? One row = one credit card transaction

Step 3: Identify the Dimensions

What Dimensions?

  • Customer
  • Card
  • Merchant
  • Date
  • Time
  • Location
  • Transaction Type

Step 4: Identify the Facts

What Facts?

  • Transaction Amount
  • Fee Amount
  • Tax Amount
  • Reward Points

Finally:

                  Dim Customer
                       |
                       |
Dim Card ------- Fact Transaction ------ Dim Merchant
                       |
                       |
                    Dim Date
                       |
                       |
                    Dim Time

This is Kimball’s thought.

Implementation – Initially Physical Model

Assuming, I have decided:
Grain = one row per sales order line

#  Fact
CREATE TABLE fact_sales (
    sales_date_key       INT,
    customer_key         BIGINT,
    product_key          BIGINT,
    store_key            BIGINT,

    order_number         STRING,

    quantity             DECIMAL(18,2),
    unit_price            DECIMAL(18,2),
    discount_amount       DECIMAL(18,2),
    sales_amount          DECIMAL(18,2),
    cost_amount           DECIMAL(18,2)
);



#  Dimensions:
CREATE TABLE dim_customer (
    customer_key BIGINT,
    customer_id  STRING,
    customer_name STRING,
    province STRING,
    customer_segment STRING
);

CREATE TABLE dim_product (
    product_key BIGINT,
    product_id STRING,
    product_name STRING,
    category STRING,
    brand STRING
);

CREATE TABLE dim_date (
    date_key INT,
    calendar_date DATE,
    year INT,
    quarter INT,
    month INT,
    month_name STRING,
    day_of_week STRING
);

Grain:Fact Table

The grain defines exactly what one row in a fact table represents.
Grain 定义 Fact Table 中“一行数据到底代表什么业务事件”。

One row = one sales transaction
一行 = 一笔销售交易

One row = one sales transaction
一行 = 一笔销售交易

One row = one order line
一行 = 一个订单明细

Grain is the semantic contract of the fact table.

Three common fact grains in Kimball

In data warehousing, these three grains are formally known as:

  1. Transaction Fact grain (事务粒度)
  2. Periodic snapshot Fact grain (周期快照粒度)
  3. Accumulating snapshot Fact grain (累积快照粒度)
Fact TypeGrain核心问题
Transaction FactOne business event
一次业务事件
What happened?
发生了什么?
Periodic SnapshotOne entity/process at a periodic point
某个周期时点的状态
What was the state?
当时状态如何?
Accumulating SnapshotOne process lifecycle
一个业务流程生命周期
How did the process progress?
流程进行到哪里?

Transaction Fact Grain

One row represents one business transaction/event.

e.g. One row = one order line

DateCustomerProductStoreQtySales
Sep 13C001P001S00111200
Sep 13C001P002S0012500
Sep 13C001P003S001150

Periodic snapshot Fact grain

A row represents the state of a business process at a regular point in time.
一行代表某个业务对象/流程在一个固定周期时间点的状态。

e.g. Account Balance Snapshot
One row = Customer Account + Day

DateAccountBalance
Sep 11A00110,000
Sep 12A0019,500
Sep 13A00111,000

Accumulating snapshot Fact grain

A single row represents the entire lifecycle of a process or a specific entity that moves through multiple, predictable milestones.

e.g. Order Fulfillment

Order Created
↓
Payment Approved
↓
Warehouse Processing
↓
Shipped
↓
Delivered

We can represent the entire lifecycle of an order in one row.

OrderOrder DatePayment DateShip DateDelivery Date
10001Sep 10Sep 10Sep 11Sep 13

Grain Determines Whether a Fact Is Valid

A fact is only meaningful if it is measured at the same grain as the fact table.
一个 Fact 只有在与 Fact Table 的 Grain 相匹配时,才是有意义、可以正确分析的。

Suppose we define:

Grain: One row represents one Order Line.

That means every row in the fact table represents exactly:

One product on one order

OrderProductQuantitySales Amount
O1001iPhone1$1,200
O1001AirPods2$500
O1002MacBook1$2,000

A error fact example.

OrderProductSales AmountOrder Total
O1001iPhone1,2001,700
O1001AirPods5001,700

there is issue on the column “Order Total”
把 $1,700 放进一个 Order-Line Grain 的 Fact Table,并让它看起来像每一行的可加 Fact,是错误的设计。 我们的 Fact Grain 是 “One row represents one Order Line.” 一行一个order 的 “行” – order 上的一行。

Order Total O1001 这份order的 合计金额, 是 “Order Grain” – 一份order的. 放在目前的Fact 上就不对。

如果一定要使用 “Order Total ”, 那就再叫另外一个Fact_order,

Grain:One row per Order

OrderCustomerOrder TotalShipping Cost
O1001C001170020
O1002C002200030

Grain Determines Dimension Context

Kimball-style terminology: The grain defines not only what the fact represents, but also which dimensions can validly describe the row.

Dimensions

A Dimension is a business-oriented structure that provides the descriptive context used to analyze facts.
用于描述和分析 Fact 的业务分析结构。

A Very Important Kimball Principle

Kimball Principle: Dimensions should be understandable to business users.

Data Modeling

Data Modeling is the process of deciding how business data should be

  • structured,
  • organized,
  • related,
  • represented

so that people and computer systems can use it correctly.

In simple terms: Data Modeling = deciding what data we have, what each piece of data means, how different data is related, and how we should organize it.

Data Modeling include:

  • Conceptual Modeling
  • Logical Modeling
  • Physical Modeling
  • Operational Data Modeling
  • Relational Modeling
  • Data Vault
  • Dimensional Modeling
    e.g. Kimball dimensional modeling
  • Other Modeling Approaches

What is Data Modeling actually DOING?

We take the real business world and translate it into a structured data representation that a computer can store, query, analyze, and maintain.

Data Modeling is about MEANING, not just tables.

The basic building blocks of Data Modeling

English中文
Entity实体: 我们要管理的“东西”

可识别 + 业务相关 + 可以被描述
An Entity is a distinct business object or concept that we want to represent in data.

Identifiable + Relevant + Describable
Attribute属性: 描述这个东西的信息An Attribute describes a property or characteristic of an entity.
Relationship关系: 东西和东西之间怎么关联
Cardinality基数 / 数量关系

一个对多少个
Cardinality specifies how many instances of one Entity can be associated with an instance of another Entity.
1:1, 1:N, N:1, N:N
Key键Cardinality specifies how many instances of one Entity can be associated with an instance of another Entity.
simple, “Which exact record is this?”

A Primary Key is the chosen attribute or combination of attributes that uniquely identifies each row in a table.
A Foreign Key is an attribute in one entity/table that references the key of another entity/table.
Constraint约束

The three levels of Data Modeling

Conceptual Model

Conceptual Model is a high-level representation of the major business entities and their relationships.

Simply, what business things exist? 业务上有什么东西?

Logical Model

The Logical Data Model defines the detailed structure of the data, including entities, attributes, relationships, keys, and business rules, without depending on a specific database technology.

Simply, how should the business data be logically organized?

Physical Model

The Physical Data Model describes how the logical model will actually be implemented in a specific database, data warehouse, lakehouse, or other technology.

Simply, how will we actually store and implement the data?

ModelQuestion中文
ConceptualWhat?有什么业务东西?
LogicalHow is it organized?数据逻辑上怎么组织?
PhysicalHow is it implemented?技术上怎么真正实现?

What is Normalization?

Normalization is a data modeling technique used to organize data so that:

  • unnecessary duplication is reduced
  • data dependencies are correctly represented
  • update anomalies are minimized
  • data integrity is improved

Why do we normalize data?

Normalization organizes data around business entities and dependencies. It reduces unnecessary redundancy and prevents update, insert, and delete anomalies while improving data integrity and maintainability.
Normalization 是根据业务实体和数据依赖关系来组织数据,减少不必要的数据冗余,避免 Update、Insert 和 Delete Anomalies,同时提高数据完整性和可维护性。

First Normal Form — 1NF

A table is in 1NF when each attribute contains atomic values, meaning each cell represents a single value rather than a repeating group or list.
每个 Cell 应该保存一个 Atomic Value(原子值),而不是一个列表或重复组。

Second Normal Form — 2NF

A table is in 2NF when:

  1. It is already in 1NF 首先必须满足 1NF
  2. Every non-key attribute depends on the whole primary key, not only part of a composite key. 所有非 Key 属性必须依赖于整个 Primary Key, 不能只依赖 Composite Key 的一部分

Third Normal Form — 3NF

A table is in 3NF when:

  1. It is in 2NF
  2. Non-key attributes depend on the key, the whole key, and nothing but the key.

Grain

粒度

The grain defines exactly what one row in a fact table represents.
Grain 定义 Fact Table 中“一行数据到底代表什么业务事件”。

Key Takeaways

  1. Data Modeling translates the real business world into structured data.
    是把现实业务世界转换成结构化的数据。
  2. An Entity represents a business object or concept.
    表示一个业务对象或业务概念。
  3. An Attribute describes an Entity.
    描述 Entity 的特征
  4. A Relationship describes how Entities are connected.
    描述不同 Entity 之间如何关联。
  5. Cardinality describes how many instances of one Entity can relate to another.
    描述两个 Entity 之间“一对一、一对多、多对多”的数量关系。

Lakeflow Declarative Pipelines – Delta Live Tables (DLT)

Delta Live Tables (DLT) is the former name of what Databricks now calls Lakeflow Declarative Pipelines.

In other words: Lakeflow Declarative Pipelines is the current evolution and product name of DLT.

Lakeflow Declarative Pipelines is a data-pipeline framework in Databricks for defining, executing, and maintaining data processing pipelines using a declarative programming model.

The key idea is:
You define the desired state and data relationships of your datasets, rather than explicitly controlling every execution step.
你描述“数据应该是什么样、数据之间是什么关系、数据质量应该满足什么条件”,而不是手工控制每一个执行步骤。

A Declarative Pipeline can contain:

  • data sources
  • data flows
  • streaming tables
  • materialized views
  • transformations
  • data-quality rules
  • dependencies between datasets
  • pipeline configuration
  • operational and monitoring information

The pipeline engine uses the definitions to construct a dependency graph / DAG and determine how the pipeline should be executed.

In current Databricks terminology, the technology formerly known as Delta Live Tables (DLT) has evolved into Lakeflow Declarative Pipelines. The underlying open framework is also referred to as Apache Spark Declarative Pipelines (SDP).

Our Data platform:

ADLS
↓
Bronze
↓
Silver
↓
Gold

Traditional data engineering flow looks this:

ADF Trigger
      ↓
Databricks Job
      ↓
Notebook 1
      ↓
Notebook 2
      ↓
Notebook 3
      ↓
Data Quality Job
      ↓
Aggregation Job
      ↓
Power BI Refresh
 

Data Engineer have to consider and orchestrate

When does it run?                         什么时候运行?
What are the dependencies?                谁依赖谁?
What is the execution order?              谁先执行?
How are failures handled?                 失败以后怎么办?
How do we handle re-runs / retries?       重新执行怎么办?
How is incremental processing handled?    如何处理增量?
How do we avoid duplicate data?           如何避免重复?
How is data quality enforced/validated?   如何做数据质量?
How is the pipeline monitored?            如何监控?

Declarative Pipeline does this:

Bronze = source data

Silver = cleaned Bronze data

Gold = business aggregation of Silver

------------------------

Silver depends on Bronze

Gold depends on Silver

-----------------------
Bronze
   ↓
Silver
   ↓
Gold

This is called Dependency Graph.

The important architectural shift is: Execution logic becomes separated from data logic. 把 Data Logic 和 Execution Logic 分离。

Your code primarily describes:
(Data Engineers are more focused on: Data Engineer 更关注)

  • What data should exist? 数据是什么?
  • What transformation defines it? 数据怎么转换?
  • What quality rules must it satisfy? 数据质量要求是什么?
  • What datasets does it depend on? 数据依赖谁?

The platform can then manage much of (而 Pipeline Framework 更多负责):

  • Execution 怎么执行
  • Dependency resolution 依赖怎么解析
  • Incremental processing 怎么增量更新
  • Pipeline updates
  • Monitoring 怎么监控
  • Data quality metrics
  • Recovery 怎么处理失败
  • Parallel execution 哪些任务可以并行
                    Declarative Pipeline
                           │
          ┌────────────────┼────────────────┐
          │                │                │
          ↓                ↓                ↓
      Ingestion       Transformation    Data Quality
          │                │                │
     Auto Loader       Spark SQL       Expectations
          │                │                │
          ↓                ↓                ↓
       Bronze  ───────→  Silver  ─────→ Gold
          │
          ↓
      Delta Lake
          │
          ↓
   Unity Catalog

What exactly is the core problem that Declarative Pipelines (DP) aim to solve?

Assuming, a Bank, has those everyday:

Customer
Account
Transaction
Payment
Loan

They come from

Oracle
SQL Server
Kafka
ADLS
REST API

Data Engineer should create:

                Sources
                   │
       ┌───────────┼───────────┐
       ↓           ↓           ↓
    Oracle       Kafka       ADLS
       │           │           │
       └───────────┼───────────┘
                   ↓
                Bronze
                   ↓
                Silver
                   ↓
                 Gold
                   ↓
              Power BI

If we do not have a good pipeline framwork, data engineer may create

ADF Pipeline 1
ADF Pipeline 2
ADF Pipeline 3
Databricks Job 1
Databricks Job 2
Databricks Job 3
Notebook 1
Notebook 2
Notebook 3
DQ Job
CDC Job

maintaining

dependencies
schedules
retries
checkpoints
parameters
error handling
monitoring

One of the primary goals of Declarative Pipelines (DP) is to shift more of this complexity onto the pipeline framework.”

Key foundational concepts of Declarative Pipelines

Declarative Pipelines
│
├── Pipeline
│
├── Dataset
│   ├── Streaming Table
│   ├── Materialized View
│   └── Temporary View
│
├── Flow
│
├── Dependency Graph
│
├── Incremental Processing
│
├── Data Quality
│   └── Expectations
│
├── Change Data Capture
│   └── AUTO CDC
│
├── Batch Processing
│
├── Streaming Processing
│
├── Monitoring
│   └── Event Log
│
├── Unity Catalog
│
└── Deployment / CI-CD

Pipeline / Dataset / Flow

ConceptProfessional Meaning人话
PipelineApplication / execution boundary整个工厂
DatasetData object produced/maintained产品
FlowData movement / processing path传送带
DependencyRelationship between datasets谁必须等谁
Pipeline EngineExecutes the declared pipeline工厂管理系统

Declarative Pipeline Python API code implement

# ============================================================
# Import the modern Declarative Pipelines API
# ============================================================

from pyspark import pipelines as dp


# ============================================================
# BRONZE DATASET
# ============================================================
#
# Purpose:
#   Define the Bronze dataset.
#
# Important:
#   We are DECLARING what this dataset should contain.
#   We are not manually starting a streaming query.
# ============================================================

@dp.table
def customer_bronze():

    return (
        spark.readStream

        # ----------------------------------------------------
        # Auto Loader
        #
        # Purpose:
        #   Incrementally discover new files arriving in ADLS.
        # ----------------------------------------------------
        .format("cloudFiles")

        # ----------------------------------------------------
        # Source format
        # ----------------------------------------------------
        .option("cloudFiles.format", "json")

        # ----------------------------------------------------
        # Source location
        # ----------------------------------------------------
        .load("/mnt/raw/customer")
    )


# ============================================================
# SILVER DATASET
# ============================================================
#
# Purpose:
#   Clean and standardize Bronze customer data.
#
# Dependency:
#
#       customer_bronze
#              ↓
#       customer_silver
#
# The dependency is expressed by reading customer_bronze.
# ============================================================

@dp.table
def customer_silver():

    return (
        spark.readStream

        # ----------------------------------------------------
        # Read the Bronze dataset.
        #
        # This establishes the logical dependency:
        #
        # customer_silver
        #        ↓
        # customer_bronze
        # ----------------------------------------------------
        .table("customer_bronze")

        # ----------------------------------------------------
        # Data quality / business rule:
        #
        # A customer record without customer_id is not useful
        # to downstream business processing.
        # ----------------------------------------------------
        .filter("customer_id IS NOT NULL")

        # ----------------------------------------------------
        # Keep only columns required by the Silver layer.
        # ----------------------------------------------------
        .select(
            "customer_id",
            "name",
            "email",
            "updated_at"
        )
    )

Step by Step build a Complete Lakeflow Declarative Pipeline

Customer Data Lakehouse Pipeline — Requirements

1. Project Goal / 项目目标

Build an end-to-end Lakeflow Declarative Pipeline for customer data:

ADLS Landing → Bronze → Data Quality / Routing → Silver → Gold

The pipeline must demonstrate:

  • Auto Loader incremental ingestion
  • Bronze raw-data preservation
  • Data Quality validation
  • Invalid-record quarantine
  • Severe-invalid record drop with audit logging
  • Silver trusted customer data
  • Gold business/analytics output
  • Declarative Pipeline dependency management
  • Incremental processing
  • Auditability and traceability

中文

我们建立一个完整的 Lakeflow Declarative Pipeline:

ADLS Landing → Bronze → 数据质量检查/路由 → Silver → Gold

这个实验不是简单地:

读数据 → filter → 写 Silver

而是模拟一个比较接近企业实际的 Data Engineering Pipeline。

重点学习:

  • Auto Loader 增量 ingestion
  • Bronze 保存原始数据
  • Data Quality validation
  • Bad data Quarantine
  • 严重坏数据 Drop + Audit Log
  • Silver Trusted Data
  • Gold Analytics Data
  • Declarative Pipeline 自动 dependency management
  • Incremental processing
  • 数据可追溯性和审计

2. Source Data / 数据源

Raw Data:
customer_001.json
customer_002.json
customer_003.json

ADLS Location: abfss://landing@.dfs.core.windows.net/customer/

ADLS
└── landing
    └── customer
        ├── customers_001.json
        ├── customers_002.json
        └── customers_003.json

Our pipeline will implement a simple Medallion Architecture using Lakeflow Declarative Pipelines.

                Azure ADLS
                    │
                    │ JSON files
                    ▼
              Auto Loader
                    │
                    ▼
          ┌───────────────────┐
          │ Bronze             │
          │ customer_bronze    │
          │ Raw / append       │
          └─────────┬─────────┘
                    │
                    │ transformation
                    ▼
          ┌───────────────────┐
          │ Silver            │
          │ customer_silver   │
          │ Cleaned data      │
          └─────────┬─────────┘
                    │
                    │ aggregation
                    ▼
          ┌───────────────────┐
          │ Gold              │
          │ customer_summary  │
          │ Business data     │
          └───────────────────┘

Customer Business Columns

ColumnMeaning
customer_idCustomer identifier
nameCustomer name
emailCustomer email
statusACTIVE / INACTIVE
countryCustomer country
updated_atSource/business update timestamp

3. Bronze Requirement / Bronze 要求

Bronze 是整个 pipeline 的原始数据入口层。Ingestion 必须使用:Auto Loader. 要能够:support scalable file ingestion
incremental discover new files
process new files
avoid reprocessing already processed files

4. Bronze Must Preserve Raw Data

这是非常重要的一条 requirement。Bronze 不能只保存清洗后的 business columns。Bronze 必须尽可能保留:

Business Data

customer_id
name
email
status
country
updated_at

Raw / Audit Metadata

例如:

_raw_record
_ingest_ts
_source_file
_source_file_name
_source_file_modification_time

如果适用,也可以考虑:

_source_system
_batch_id
_record_hash

5. Important Timestamp Definition

这里要特别区分两个时间, updated_at and _ingest_ts

updated_at: 这是 Source Business Timestamp, 表示源系统认为这条 customer record 什么时候更新。例如: updated_at = 2026-09-10 10:05:00

_ingest_ts: 这是 Data Platform Ingestion Timestamp, 表示这条数据什么时候进入我们的 Data Platform / Pipeline processing。例如:_ingest_ts = 2026-09-10 10:06:13

两者不能混为一谈。

6. Bronze Raw Record Requirement

我们希望 Bronze 能够保存原始 record 的 JSON representation,例如:_raw_record

这样以后:如果一条 record 被 quarantine 或 drop,我们可以看到它进入 pipeline 时的原始状态。这是企业 Data Platform 中非常重要的:Traceability / 可追溯性

7. Data Quality Requirements

这是整个实验最重要的部分。我们定义三个核心 Data Quality rules:

Rule 1 — Customer ID

customer_id must not be NULL

Rule 2 — Email

Email 必须:

NOT NULL

并且应该满足基本 email format。例如:alice@example.com 有效。而:alice@ 无效。

Rule 3 — Status

Status 必须是:ACTIVE 或者:INACTIVE . 其他值都是 invalid。 例如:

ACTIVE       → valid
INACTIVE     → valid
PENDING      → invalid
UNKNOWN      → invalid
NULL         → invalid

8. Data Quality Routing Requirement

我们不是简单地 DROP 所有坏数据。这是这个项目的核心设计。我们把 record 分成三个类别:

                    Bronze
                       │
                       ▼
                Data Quality
                   Validation
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
        VALID      RECOVERABLE    SEVERE
          │            │            │
          ▼            ▼            ▼
       Silver      Quarantine    Drop Log

9. Routing Decision Matrix

这是目前我们确认的正式 requirement baseline。

Customer_IDEmailStatusActionDestination
ValidValidACTIVEACCEPTSilver
ValidValidINACTIVEACCEPTSilver
NULLValidValidQUARANTINEQuarantine
ValidNULLValidQUARANTINEQuarantine
ValidValidInvalidQUARANTINEQuarantine
NULLNULLValidQUARANTINEQuarantine
NULLValidInvalidQUARANTINEQuarantine
ValidNULLInvalidQUARANTINEQuarantine
NULLNULLInvalidDROPDrop Log

10. Quarantine Requirement

如果 record 不符合正常 Silver 数据质量要求,但是没有达到 Severe Drop 条件:Quarantine

例如:

customer_id = NULL
email = bob@example.com
status = ACTIVE

不能进入 Silver。但是:不能直接删除。应该进入:

customer_quarantine

11. Quarantine Must Preserve Original Record

Quarantine 至少应该包含:Original Business Data

customer_id
name
email
status
country
updated_at

Original Record

_raw_record

Source Information

_source_file
_source_file_name

Ingestion Information

_ingest_ts

Data Quality Information

_dq_customer_id_valid
_dq_email_valid
_dq_status_valid

以及:

_dq_reason

例如:

CUSTOMER_ID_NULL

或者:

EMAIL_NULL

或者:

INVALID_STATUS

多个问题:

CUSTOMER_ID_NULL;EMAIL_NULL

12. Quarantine Timestamp

需要记录:_quarantine_ts . 表示:这条 record 被判定并进入 quarantine 的时间。

13. Drop Requirement

最严重的情况:

customer_id = NULL
email       = NULL
status      = INVALID

这条 record:不进入 Silver, 不作为正常 Quarantine Record. 而是:DROP

但是:Drop ≠ 数据完全消失,

这是非常重要的概念。真正的设计是:

Bronze
   │
   └── severe invalid
             │
             ▼
        Drop Audit Log

所以我们实际上是:从 business processing path 中 drop,但从 audit trail 中保留。

14. Drop Log Requirement

建立:customer_drop_log

至少保存:

Original Data

customer_id
name
email
status
country
updated_at

Raw Record

_raw_record

Source Metadata

_source_file
_source_file_name
_source_file_modification_time

Ingestion Information

_ingest_ts

Drop Information

_drop_ts
_drop_reason

例如:

_drop_reason =
CUSTOMER_ID_NULL;EMAIL_NULL;INVALID_STATUS

这样以后可以回答:Why was this customer record dropped?

以及:What exactly was the original record?

15. Silver Requirement

Silver 是:Trusted / Validated Customer Data

只有:

customer_id valid
AND
email valid
AND
status ∈ ('ACTIVE', 'INACTIVE')

的 record 才能进入 Silver。因此:

Bronze
   │
   ├── valid ─────────────→ Silver
   │
   ├── invalid/recoverable → Quarantine
   │
   └── severe invalid ────→ Drop Log

Silver 不应该包含:

  • NULL customer_id
  • NULL email
  • invalid email
  • invalid status

16. Silver Transformation Requirement

Silver 除了 DQ filtering,还可以做一些正常的 standardization,例如:

trim(customer_id)
trim(name)
lower(email)
upper(status)

例如:

" alice@example.com "

standardize 成:

"alice@example.com"

但是:Bronze 不修改原始数据。这是:

Bronze = Preserve
Silver = Clean / Standardize / Trust

17. Gold Requirement

Gold 是面向:Business / Analytics Consumption. 我们需要一个简单但有意义的 Gold dataset。

例如:

customer_summary

按:

status

统计 customer 数量:

status      customer_count
---------------------------
ACTIVE      5
INACTIVE    1

也可以进一步按:

country
status

进行 aggregation。

例如:

country    status       customer_count
---------------------------------------
Canada     ACTIVE       4
Canada     INACTIVE     0
USA        INACTIVE     1
UK         ACTIVE       1

Gold 使用:Materialized View 来体现:

Silver
   ↓
Gold Materialized View

18. Final Pipeline Architecture

                         ADLS
                          │
                          │
                    Auto Loader
                          │
                          ▼
                    ┌───────────┐
                    │  Bronze   │
                    │ Raw Data  │
                    │ + Metadata│
                    └─────┬─────┘
                          │
                          ▼
                  Data Quality Rules
                          │
             ┌────────────┼────────────┐
             │            │            │
             ▼            ▼            ▼
          VALID      RECOVERABLE     SEVERE
             │            │            │
             ▼            ▼            ▼
          Silver      Quarantine    Drop Log
             │
             │
             ▼
       Gold Materialized
             View

19. Final Dataset List

整个 Pipeline 最终应该至少有 4 个主要数据集:

1. customer_bronze
2. customer_silver
3. customer_quarantine
4. customer_drop_log
5. customer_summary        ← Gold

也就是说:

Bronze
   │
   ├──────────────→ Silver
   │                   │
   │                   ▼
   │                  Gold
   │
   ├──────────────→ Quarantine
   │
   └──────────────→ Drop Log

20. Declarative Pipeline Requirement

我们不希望自己手工 orchestration, 而是 Declare the datasets and their relationships.

Silver reads Bronze
Gold reads Silver
Quarantine reads Bronze
Drop Log reads Bronze

Pipeline framework 根据这些 dependencies 自动建立:

Dependency Graph

然后负责:

  • execution
  • dependency resolution
  • orchestration
  • incremental processing
  • checkpointing
  • pipeline updates
  • table maintenance

我们的代码主要负责:定义“数据应该是什么样子,以及它从哪里来”。

22. Incremental Processing Requirement

实验必须体现:First Run 例如:customers_001.json ; customers_002.json, 进入 pipeline。

Later 再增加:customers_003.json

Pipeline update 后:只处理新的 input data,而不是每次重新从头读取所有文件。

最终我们观察:

Bronze
Silver
Quarantine
Drop Log
Gold

23. Data Lineage / Traceability Requirement

我们最终应该能够回答:Where did this Silver record come from?

Silver
 ↓
Bronze
 ↓
ADLS file

Why was this record quarantined?

Quarantine
 ↓
DQ reason
 ↓
Original record
 ↓
Source file

Why was this record dropped?

Drop Log
 ↓
Drop reason
 ↓
Original record
 ↓
Source file
 ↓
Ingestion timestamp

这就是:

Data Lineage + Auditability + Traceability

25. Coding Order / 我们之后的 Coding 顺序

Step 1: Create Bronze

ADLS
 ↓
Auto Loader
 ↓
customer_bronze

Step 2: Validate Bronze Data

建立:

customer_id validation
email validation
status validation

Step 3: Classify / Route Records

VALID
QUARANTINE
DROP

Step 4: Create Quarantine

customer_quarantine

Step 5: Create Drop Audit Log

customer_drop_log

Step 6: Create Silver

customer_silver

Step 7: Create Gold

customer_summary

Step 8: Test Incremental Processing

增加新的 JSON:

customers_003.json

Step 9: Review Pipeline Graph / Data Quality / Audit

Test Data

we want to demonstrate incremental ingestion.

# File 1 — customers_001.json

[
  {
    "customer_id": "C001",
    "name": "Alice",
    "email": "alice@example.com",
    "status": "ACTIVE",
    "country": "Canada",
    "updated_at": "2026-09-10T09:00:00"
  },
  {
    "customer_id": "C002",
    "name": "Bob",
    "email": "bob@example.com",
    "status": "ACTIVE",
    "country": "Canada",
    "updated_at": "2026-09-10T09:05:00"
  },
  {
    "customer_id": "C003",
    "name": "Charlie",
    "email": "charlie@example.com",
    "status": "INACTIVE",
    "country": "USA",
    "updated_at": "2026-09-10T09:10:00"
  }
]





# File 2 — customers_002.json
[
  {
    "customer_id": "C004",
    "name": "David",
    "email": "david@example.com",
    "status": "ACTIVE",
    "country": "Canada",
    "updated_at": "2026-09-10T10:00:00"
  },
  {
    "customer_id": "C005",
    "name": "Emma",
    "email": "emma@example.com",
    "status": "ACTIVE",
    "country": "UK",
    "updated_at": "2026-09-10T10:05:00"
  }
]

URL: abfss://landing@storageaccount.dfs.core.windows.net/customer/

Pipeline source code will like:

customer_pipeline/
│
├── customer_pipeline.py
│
└── configuration

1: Create Bronze (Simple, no for product)

aim: Incrementally load JSON files from ADLS into a Bronze Streaming Table.

# 简单实现, 其实在实际项目不会这么简单
from pyspark import pipelines as dp


@dp.table 
def customer_bronze():        # create a table called "customer_bronze" 
   # The decorator declares that the function defines a pipeline-managed dataset.
   # In this context, it creates a streaming table.

    return (
        spark.readStream
            .format("cloudFiles")
            .option("cloudFiles.format", "json")
            .load("abfss://landing@myaccount.dfs.core.windows.net/customer/")
    )

we do not do ” create table ……” then “writeStream.start()” ,

Declarative Pipeline WILL TAKE OVER this job

The pipeline framework manages:

  • execution
  • checkpointing
  • dependency resolution
  • incremental processing
  • scheduling
  • table maintenance
  • orchestration

The developer primarily declares: what dataset should exist and how it is derived.

2: Create Silver (Simple, not for product)

@dp.table
@dp.expect(                          # Data Quality Expectation
    "valid_customer_id",
    "customer_id IS NOT NULL"        # customer_id cannot null
)

@dp.expect(
    "valid_email",
    "email IS NOT NULL AND email RLIKE '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'"
                                           # email cannot be null and it should be in good form
)

@dp.expect(
    "valid_status",
    "status IN ('ACTIVE', 'INACTIVE')"    # status only allow ACTIVE or INACTIVE
)

def customer_silver():

    return (
        spark.readStream
            .table("customer_bronze")
            .filter("customer_id IS NOT NULL")
            .select(
                "customer_id",
                "name",
                "email",
                "status",
                "country",
                "updated_at"
            )
    )

Product grade, end- to-end code implement

test data

abfss://landing@<storage-account>.dfs.core.windows.net/customer/

customers_001.json
customers_002.json

[
  {
    "customer_id": "C001",
    "name": "Alice",
    "email": "alice@example.com",
    "status": "ACTIVE",
    "country": "Canada",
    "updated_at": "2026-09-10T09:00:00"
  },
  {
    "customer_id": "C002",
    "name": "Bob",
    "email": "bob@example.com",
    "status": "ACTIVE",
    "country": "Canada",
    "updated_at": "2026-09-10T09:05:00"
  },
  {
    "customer_id": "C003",
    "name": "Charlie",
    "email": "charlie@example.com",
    "status": "INACTIVE",
    "country": "USA",
    "updated_at": "2026-09-10T09:10:00"
  }
]

Step 1 — Create Bronze

Our first pipeline layer is:

ADLS Landing
      │
      │ Auto Loader
      ▼
customer_bronze

Bronze’s job is not to clean the data. Its job is: Ingest and preserve the source data faithfully, together with ingestion/source metadata.

First Bronze implementation

from pyspark import pipelines as dp


@dp.table
def customer_bronze():  # create a table called "customer_bronze" 

    return (
        spark.readStream
            .format("cloudFiles")
            .option("cloudFiles.format", "json")
            .load(
                "abfss://landing@<storage-account>.dfs.core.windows.net/customer/"
            )
    )

What does this code actually produce? it creates “customer_bronze”

Table customer_bronze:

customer_id
name
email
status
country
updated_at

# we're missing
├── _raw_record
├── _ingest_ts
├── _source_file
├── _source_file_name
└── _source_file_modification_time
这些是我们之前 Requirement 中要求的 audit information。只有改成下面的

现在我们把代码升级, 满足requirement 的要求。

from pyspark import pipelines as dp
from pyspark.sql.functions import (
    current_timestamp,
    col,
    struct,
    to_json
)


@dp.table(
    comment="Bronze layer containing raw customer data ingested incrementally from ADLS using Auto Loader."
)
def customer_bronze():

    return (
        spark.readStream

            # ---------------------------------------------------------
            # Use Databricks Auto Loader as the streaming file source.
            # Auto Loader incrementally discovers new files arriving
            # in the ADLS landing folder.
            # ---------------------------------------------------------
            .format("cloudFiles")

            # ---------------------------------------------------------
            # The source files in our experiment are JSON files.
            # ---------------------------------------------------------
            .option("cloudFiles.format", "json")

            # ---------------------------------------------------------
            # Load files from the ADLS landing zone.
            #
            # Replace <storage-account> with the real storage account.
            # ---------------------------------------------------------
            .load(
                "abfss://landing@<storage-account>.dfs.core.windows.net/customer/"
            )

            # ---------------------------------------------------------
            # Add platform ingestion timestamp.
            #
            # This is NOT the source business timestamp.
            # updated_at = source/business time
            # _ingest_ts = data-platform ingestion/processing time
            # ---------------------------------------------------------
            .withColumn(
                "_ingest_ts",
                current_timestamp()
            )

            # ---------------------------------------------------------
            # Capture source file metadata.
            #
            # _metadata is provided by Auto Loader.
            # We preserve the file path and modification time so that
            # downstream DQ issues can be traced back to the source file.
            # ---------------------------------------------------------
            .withColumn(
                "_source_file",
                col("_metadata.file_path")
            )
            .withColumn(
                "_source_file_modification_time",
                col("_metadata.file_modification_time")
            )

            # ---------------------------------------------------------
            # Keep the raw source record as JSON.
            #
            # This gives us a representation of the record as it exists
            # at the Bronze stage before Silver transformations.
            #
            # This will later be extremely useful for:
            #   - Quarantine
            #   - Drop Audit Log
            #   - Troubleshooting
            #   - Data lineage / traceability
            # ---------------------------------------------------------
            .withColumn(
                "_raw_record",
                to_json(
                    struct(
                        "customer_id",
                        "name",
                        "email",
                        "status",
                        "country",
                        "updated_at"
                    )
                )
            )

            # ---------------------------------------------------------
            # Select the columns that belong to the Bronze contract.
            #
            # Bronze preserves business data plus source/audit metadata.
            # ---------------------------------------------------------
            .select(
                "customer_id",
                "name",
                "email",
                "status",
                "country",
                "updated_at",

                "_raw_record",
                "_ingest_ts",

                "_source_file",
                "_source_file_modification_time"
            )
    )

三个时间的区别 (以后你面试很可能会被问到)

FieldMeaning
updated_atSource business timestamp
_source_file_modification_timeSource file timestamp
_ingest_tsPlatform ingestion timestamp

为什么需要 _raw_record? 这是我们后面设计:

Quarantine
Drop Log

Current Bronze Architecture

现在我们已经从:

ADLS
 ↓
Auto Loader
 ↓
Bronze

升级成:

                         ADLS
                          │
                          ▼
                    Auto Loader
                          │
                          ▼
                ┌──────────────────┐
                │ customer_bronze  │
                ├──────────────────┤
                │ customer_id      │
                │ name             │
                │ email            │
                │ status           │
                │ country          │
                │ updated_at       │
                │                  │
                │ _raw_record      │
                │ _ingest_ts       │
                │ _source_file     │
                │ _source_file_... │
                └──────────────────┘

What should we see after the first run? 如果 source 中:

C001
C002
C003
C004
C005

那么 Bronze 应该有:5 records. 例如:

customer_idemailstatus_source_file_ingest_ts
C001alice@example.comACTIVEcustomers_001.json…
C002bob@example.comACTIVEcustomers_001.json…
C003charlie@example.comINACTIVEcustomers_001.json…
C004david@example.comACTIVEcustomers_002.json…
C005emma@example.comACTIVEcustomers_002.json…

注意:我们还没有进行任何 Business Data Quality filtering。

所以把代码补完整:

from pyspark import pipelines as dp
from pyspark.sql.functions import (
    current_timestamp,
    col,
    struct,
    to_json
)

@dp.table(
    comment="Bronze layer containing raw customer data ingested incrementally from ADLS using Auto Loader."
)
def customer_bronze():

    return (
        spark.readStream
            .format("cloudFiles")
            .option("cloudFiles.format", "json")
            .load(
                "abfss://landing@<storage-account>.dfs.core.windows.net/customer/"
            )

            # Platform ingestion timestamp
            .withColumn(
                "_ingest_ts",
                current_timestamp()
            )

            # Source file metadata
            .withColumn(
                "_source_file",
                col("_metadata.file_path")
            )
            .withColumn(
                "_source_file_name",
                col("_metadata.file_name")
            )
            .withColumn(
                "_source_file_modification_time",
                col("_metadata.file_modification_time")
            )

            # Serialized representation of the parsed business record
            .withColumn(
                "_raw_record",
                to_json(
                    struct(
                        "customer_id",
                        "name",
                        "email",
                        "status",
                        "country",
                        "updated_at"
                    )
                )
            )

            .select(
                "customer_id",
                "name",
                "email",
                "status",
                "country",
                "updated_at",
                "_raw_record",
                "_ingest_ts",
                "_source_file",
                "_source_file_name",
                "_source_file_modification_time"
            )
    )

Step 2 — Data Quality Validation

数据质量验证

from pyspark import pipelines as dp
from pyspark.sql.functions import (
    col,
    when,
    concat_ws,
    array,
    lit
)

@dp.temporary_view
def customer_validation():

    return (
        spark.readStream
            .table("customer_bronze")

            # ==============================
            # DQ Rule 1: Customer ID
            # ==============================
            .withColumn(
                "_dq_customer_id_valid",
                col("customer_id").isNotNull()
            )

            # ==============================
            # DQ Rule 2: Email
            # ==============================
            .withColumn(
                "_dq_email_valid",
                (
                    col("email").isNotNull()
                    &
                    col("email").rlike(
                        r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
                    )
                )
            )

            # ==============================
            # DQ Rule 3: Status
            # ==============================
            .withColumn(
                "_dq_status_valid",
                col("status").isin("ACTIVE", "INACTIVE")
            )

            # ==============================
            # Overall validation
            # ==============================
            .withColumn(
                "_dq_all_valid",
                (
                    col("_dq_customer_id_valid")
                    &
                    col("_dq_email_valid")
                    &
                    col("_dq_status_valid")
                )
            )

            # ==============================
            # DQ reason
            # ==============================
            .withColumn(
                "_dq_reason",
                concat_ws(
                    ";",
                    array(
                        when(
                            col("customer_id").isNull(),
                            lit("CUSTOMER_ID_NULL")
                        ),

                        when(
                            col("email").isNull(),
                            lit("EMAIL_NULL")
                        ),

                        when(
                            col("email").isNotNull()
                            &
                            ~col("email").rlike(
                                r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
                            ),
                            lit("INVALID_EMAIL_FORMAT")
                        ),

                        when(
                            ~col("status").isin("ACTIVE", "INACTIVE"),
                            lit("INVALID_STATUS")
                        )
                    )
                )
            )

            # ==============================
            # Routing decision
            # ==============================
            .withColumn(
                "_dq_action",
                when(
                    col("_dq_all_valid"),
                    lit("ACCEPT")
                )
                .when(
                    (~col("_dq_customer_id_valid"))
                    &
                    (~col("_dq_email_valid"))
                    &
                    (~col("_dq_status_valid")),
                    lit("DROP")
                )
                .otherwise(
                    lit("QUARANTINE")
                )
            )
    )

Step 3 — Routing into Silver / Quarantine / Drop Log

Silver 的原则:Only trusted, validated customer records enter Silver.

from pyspark import pipelines as dp
from pyspark.sql.functions import (
    col,
    when,
    concat_ws,
    array,
    lit,
    trim,
    lower,
    upper,
    current_timestamp
)


# ============================================================
# 1. Validation and Routing
# ============================================================

@dp.temporary_view
def customer_validation():

    return (
        spark.readStream
            .table("customer_bronze")

            # ------------------------------------------------
            # DQ Rule 1: customer_id must not be NULL
            # ------------------------------------------------
            .withColumn(
                "_dq_customer_id_valid",
                col("customer_id").isNotNull()
            )

            # ------------------------------------------------
            # DQ Rule 2:
            # email must not be NULL and must match
            # the basic email format
            # ------------------------------------------------
            .withColumn(
                "_dq_email_valid",
                (
                    col("email").isNotNull()
                    &
                    col("email").rlike(
                        r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
                    )
                )
            )

            # ------------------------------------------------
            # DQ Rule 3:
            # status must be ACTIVE or INACTIVE
            # ------------------------------------------------
            .withColumn(
                "_dq_status_valid",
                (
                    col("status").isNotNull()
                    &
                    col("status").isin(
                        "ACTIVE",
                        "INACTIVE"
                    )
                )
            )

            # ------------------------------------------------
            # Overall DQ result
            # ------------------------------------------------
            .withColumn(
                "_dq_all_valid",
                (
                    col("_dq_customer_id_valid")
                    &
                    col("_dq_email_valid")
                    &
                    col("_dq_status_valid")
                )
            )

            # ------------------------------------------------
            # DQ Reason
            #
            # Multiple reasons are concatenated:
            #
            # CUSTOMER_ID_NULL;EMAIL_NULL
            # ------------------------------------------------
            .withColumn(
                "_dq_reason",
                concat_ws(
                    ";",
                    array(

                        when(
                            col("customer_id").isNull(),
                            lit("CUSTOMER_ID_NULL")
                        ),

                        when(
                            col("email").isNull(),
                            lit("EMAIL_NULL")
                        ),

                        when(
                            col("email").isNotNull()
                            &
                            ~col("email").rlike(
                                r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
                            ),
                            lit("INVALID_EMAIL_FORMAT")
                        ),

                        when(
                            col("status").isNull()
                            |
                            ~col("status").isin(
                                "ACTIVE",
                                "INACTIVE"
                            ),
                            lit("INVALID_STATUS")
                        )
                    )
                )
            )

            # ------------------------------------------------
            # Routing Decision
            #
            # ACCEPT:
            #   All three rules pass
            #
            # DROP:
            #   All three rules fail
            #
            # QUARANTINE:
            #   Everything else
            # ------------------------------------------------
            .withColumn(
                "_dq_action",
                when(
                    col("_dq_all_valid"),
                    lit("ACCEPT")
                )
                .when(
                    (~col("_dq_customer_id_valid"))
                    &
                    (~col("_dq_email_valid"))
                    &
                    (~col("_dq_status_valid")),
                    lit("DROP")
                )
                .otherwise(
                    lit("QUARANTINE")
                )
            )
    )


# ============================================================
# 2. Silver
# ============================================================

@dp.table(
    comment="Trusted customer records that passed all core data quality rules."
)
def customer_silver():

    return (
        spark.readStream
            .table("customer_validation")

            # Only fully valid records
            .filter(
                col("_dq_action") == "ACCEPT"
            )

            # Standardize trusted business data
            .select(
                trim(
                    col("customer_id")
                ).alias("customer_id"),

                trim(
                    col("name")
                ).alias("name"),

                lower(
                    trim(
                        col("email")
                    )
                ).alias("email"),

                upper(
                    trim(
                        col("status")
                    )
                ).alias("status"),

                trim(
                    col("country")
                ).alias("country"),

                col("updated_at"),

                # Audit / lineage metadata
                col("_raw_record"),
                col("_ingest_ts"),
                col("_source_file"),
                col("_source_file_name"),
                col("_source_file_modification_time")
            )
    )


# ============================================================
# 3. Quarantine
# ============================================================

@dp.table(
    comment="Customer records that failed one or more recoverable data quality rules."
)
def customer_quarantine():

    return (
        spark.readStream
            .table("customer_validation")

            # Recoverable DQ failures
            .filter(
                col("_dq_action") == "QUARANTINE"
            )

            .select(
                # Original business data
                "customer_id",
                "name",
                "email",
                "status",
                "country",
                "updated_at",

                # Raw / lineage information
                "_raw_record",
                "_ingest_ts",
                "_source_file",
                "_source_file_name",
                "_source_file_modification_time",

                # DQ results
                "_dq_customer_id_valid",
                "_dq_email_valid",
                "_dq_status_valid",
                "_dq_reason",

                # Quarantine audit timestamp
                current_timestamp().alias(
                    "_quarantine_ts"
                )
            )
    )


# ============================================================
# 4. Drop Log
# ============================================================

@dp.table(
    comment="Audit log for severely invalid customer records excluded from normal processing."
)
def customer_drop_log():

    return (
        spark.readStream
            .table("customer_validation")

            # Severe DQ failures
            .filter(
                col("_dq_action") == "DROP"
            )

            .select(
                # Original business data
                "customer_id",
                "name",
                "email",
                "status",
                "country",
                "updated_at",

                # Raw / lineage information
                "_raw_record",
                "_ingest_ts",
                "_source_file",
                "_source_file_name",
                "_source_file_modification_time",

                # DQ results
                "_dq_customer_id_valid",
                "_dq_email_valid",
                "_dq_status_valid",

                # Audit reason
                col("_dq_reason").alias(
                    "_drop_reason"
                ),

                # Drop audit timestamp
                current_timestamp().alias(
                    "_drop_ts"
                )
            )
    )

这四个 Dataset 的关系

                    customer_bronze
                           │
                           │
                           ▼
                ┌────────────────────┐
                │ customer_validation│
                │                    │
                │ DQ Rules           │
                │ + Reason           │
                │ + Routing          │
                └─────────┬──────────┘
                          │
             ┌────────────┼────────────┐
             │            │            │
          ACCEPT      QUARANTINE      DROP
             │            │            │
             ▼            ▼            ▼
     customer_silver   customer_     customer_
                      quarantine    drop_log

最终 Routing Logic

customer_idemailstatus_dq_action去哪里
validvalidACTIVEACCEPTSilver
validvalidINACTIVEACCEPTSilver
NULLvalidvalidQUARANTINEQuarantine
validNULLvalidQUARANTINEQuarantine
validinvalid formatvalidQUARANTINEQuarantine
validvalidinvalidQUARANTINEQuarantine
NULLNULLvalidQUARANTINEQuarantine
NULLvalidinvalidQUARANTINEQuarantine
validNULLinvalidQUARANTINEQuarantine
NULLNULLinvalidDROPDrop Log

Step 4 — Gold: customer_summary

from pyspark import pipelines as dp


@dp.materialized_view(
    comment="Gold customer summary by customer status."
)
def customer_summary():

    return (
        spark.read
            .table("customer_silver")
            .groupBy("status")
            .count()
            .withColumnRenamed(
                "count",
                "customer_count"
            )
    )

完整架构:

                       ADLS Landing
                            │
                            ▼
                     ┌─────────────┐
                     │ Auto Loader │
                     └──────┬──────┘
                            │
                            ▼
                    ┌───────────────┐
                    │    Bronze     │
                    │ Raw + Metadata│
                    └───────┬───────┘
                            │
                            ▼
                 ┌─────────────────────┐
                 │ Customer Validation │
                 │                     │
                 │ DQ Rules            │
                 │ DQ Reason           │
                 │ Routing             │
                 └─────────┬───────────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
              ▼            ▼            ▼
           ACCEPT      QUARANTINE      DROP
              │            │            │
              ▼            ▼            ▼
           Silver      Quarantine    Drop Log
              │
              ▼
        Materialized View
              │
              ▼
             Gold
              │
              ▼
     customer_summary

Lakeflow Declarative Pipeline

我们现在写的是:

@dp.table
def customer_bronze():
    ...

然后:

@dp.temporary_view
def customer_validation():
    return spark.readStream.table("customer_bronze")

然后:

@dp.table
def customer_silver():
    return spark.readStream.table("customer_validation")

最后:

@dp.materialized_view
def customer_summary():
    return spark.read.table("customer_silver")

Streaming Table vs Materialized View

Streaming TableMaterialized View
典型用途incremental row processingderived / aggregated result
数据特点持续到来的数据查询计算结果
常见操作filter / transform / enrichaggregation / business metrics
API@dp.table@dp.materialized_view
我们的例子customer_silvercustomer_summary

Step 6 — Put the Code into a Lakeflow Pipeline

现在进入真正的 hands-on 部分:

我们已经写完 Dataset definitions。下一步就是把这些 definitions 放进一个 Lakeflow Pipeline,让 Databricks 真正执行它。

这里先只做 Pipeline structure + code organization,暂时不深入 Deployment / Schedule / Production settings。

先把代码组织起来

最简单的方式是一个 Python pipeline source file,例如:

customer_pipeline/
│
└── customer_pipeline.py

里面包含我们目前的全部 Dataset definitions:

customer_pipeline.py
│
├── customer_bronze
│
├── customer_validation
│
├── customer_silver
│
├── customer_quarantine
│
├── customer_drop_log
│
└── customer_summary

Data mesh

Data mesh is a decentralized data architecture and organizational model that shifts data ownership from a single central IT team to individual business domains.

Its core idea is: Data should be owned and managed by the business domains that understand that data best, while still following common enterprise governance standards.

Instead of one central data team owning everything, different business domains become responsible for their own data products.

Enterprise
│
├── Customer Domain
│   └── Customer Data Product
│
├── Finance Domain
│   └── Finance Data Product
│
├── Risk Domain
│   └── Risk Data Product
│
├── HR Domain
│   └── Employee Data Product
│
└── Operations Domain
    └── Operations Data Product

Data Mesh, on the other hand, becomes: Domain owns the data, Enterprise governs the data.

Finance Domain → looks after Finance Data
HR Domain → looks after HR Data
Risk Domain → looks after Risk Data
Customer Domain → looks after Customer Data

What is Data Mesh trying to address?

  • Domain Ownership
    Data is owned by the business domains.
  • Data as a Product
    Data must be treated like a product.
  • Self-Serve Data Platform
    The enterprise provides a centralized platform enabling domains to autonomously publish and govern their data.
  • Federated Computational Governance
    The enterprise continues to define standardized rules.
    • Security
    • Privacy
    • Governance
    • Compliance
    • Data Quality
    • Metadata
    • Lineage

What is Domain-Driven Modeling?

Domain-Driven Modeling means: Designing data models around the business domains, business concepts, business processes, and business rules rather than starting from source-system tables.

Domain vs Canonical Model

Canonical Model ≠ Data Mesh

Data Mesh –>Reorganize data ownership, data products, the platform, and governance.

                 Enterprise
                     │
             Data Mesh Governance
                     │
        ┌────────────┼────────────┐
        ↓            ↓            ↓
    Customer       Finance       Risk
     Domain         Domain       Domain
        │            │            │
     Canonical    Canonical    Canonical
      Model         Model        Model
        │            │            │
    Data Product  Data Product  Data Product

Data Mesh targets a different set of challenges: data is owned by the business domains, it must be treated like a product, and the enterprise provides a unified self-serve platform. Ultimately, it requires reorganizing data ownership, data products, the platform, and governance.

What exactly are we modeling in Domain-Driven Design?

Business Domain

First, determine: What business area are we modeling?

  • Customer
  • Payment
  • Risk
  • Mortgage
  • Finance

Business Capability

Within a domain, we can further examine business capabilities

Customer Domain
│
├── Customer Onboarding
├── Customer Identification
├── Customer Profile Management
├── Customer Relationship Management
└── Customer Communication

Capability indicates: What the business domain can do.
Capability 表示:这个业务领域能够做什么。

Business Concept

Then, identify the truly critical business concepts within the domain.
然后识别 Domain 中真正重要的业务概念。

# Customer Domain:
Customer
Customer Profile
Customer Relationship
Customer Address
Customer Contact

# Payment Domain:
Payment
Payment Instruction
Payment Method
Payment Transaction
Payment Status

Business Event

Taking it a step further: What events occur within the domain?

e.g.

  • CustomerCreated
  • CustomerUpdated
  • CustomerAddressChanged
  • PaymentInitiated
  • PaymentCompleted
  • PaymentFailed

In Domain-Driven Design (DDD), things that “happen” are specifically called “Domain Events.”

Using the word “events” makes your phrasing sound highly sophisticated and immediately signals the right architectural concept to other engineers.

Business Rule

Domain has business rules. e.g.

Payment amount > $10,000
↓
Additional approval required

Data Entity

Finally, data modeling

Customer
Account
Payment
Address
Transaction

THen define

Attributes
Keys
Relationships
Cardinality
Constraints
History

Therefore, the overall process is as follows:

Business Domain
       ↓
Business Capability
       ↓
Business Concept
       ↓
Business Event
       ↓
Business Rule
       ↓
Domain Entity
       ↓
Data Model
       ↓
Data Product

Complete example

Step 1 — Identify Domain

Customer Domain: Manage customer identity, profile and relationship information.

Step 2 — Identify Business Concepts

Customer
Customer Profile
Customer Address
Customer Contact
Customer Relationship

Step 3 — Identify Business Events

CustomerCreated
CustomerUpdated
CustomerAddressChanged
CustomerStatusChanged

Step 4 — Identify Business Rules

e.g. Customer must have a unique enterprise customer ID.

Customer status must be one of:

  • ACTIVE
  • INACTIVE
  • SUSPENDED
  • CLOSED

Step 5 — Build Domain Model

Customer
│
├── Customer Profile
│
├── Customer Address
│
├── Customer Contact
│
└── Customer Relationship

Step 6 — Map Source Systems

CRM
   │
   ├── CRM_CUSTOMER
   └── CRM_CONTACT
             │
             ↓
Core Banking ─────→ Customer Domain
   │
   └── CUSTOMER
             │
             ↓
Mortgage
   │
   └── BORROWER

Step 7 — Canonical Model

Customer
------------------------
customer_id
customer_type
first_name
last_name
date_of_birth
status
created_date
updated_date

Step 8 — Data Product

Customer Domain provides “Customer Data Product”.

e.g. customer.gold_customer. it should has

  • Owner
  • Schema
  • Description
  • Data Quality
  • SLA
  • Security
  • Lineage
  • Documentation
  • Access Policy

As a result, the downstream Risk team does not need direct access to:

  • CRM
  • Core Banking
  • Mortgage

Instead, they consume: Customer Data Product

How to implement in Databricks

                    Enterprise Data Mesh
                            │
              Federated Governance
                            │
        ┌───────────────────┼───────────────────┐
        ↓                   ↓                   ↓
 Customer Domain       Finance Domain       Risk Domain
        │                   │                   │
        ↓                   ↓                   ↓
 Unity Catalog         Unity Catalog        Unity Catalog
        │                   │                   │
        ↓                   ↓                   ↓
 Delta Tables          Delta Tables         Delta Tables
        │                   │                   │
        ↓                   ↓                   ↓
 Data Products         Data Products        Data Products

Catalog / Schema designing

e.g.

customer_catalog
│
├── bronze
├── silver
└── gold

Do not think “One Domain = One Catalog”,
No, implementation choice, it is NOT data Mesh define

Domain-owned tables

-- ============================================================
-- PURPOSE:
-- Create the Customer domain's curated customer entity.
--
-- KEY IDEA:
-- The Customer domain owns the business meaning and quality
-- of this dataset.
-- ============================================================

CREATE TABLE customer_catalog.gold.customer
(
    -- Enterprise-wide business identifier.
    customer_id STRING NOT NULL,

    -- Business classification of the customer.
    customer_type STRING,

    -- Standardized customer name.
    first_name STRING,
    last_name STRING,

    -- Standardized date of birth.
    date_of_birth DATE,

    -- Controlled business status.
    status STRING,

    -- Audit columns.
    created_date TIMESTAMP,
    updated_date TIMESTAMP
)
USING DELTA;

Key Takeaways

Data Mesh is primarily about how an enterprise organizes ownership, products, platforms, and governance around data domains.

Domain: Business responsibility boundary

Domain-Driven Modeling: Business first –> Data model second

Canonical Model: Standardized enterprise representation of data

Data Product: A Data Product is not simply a Delta table.

Data Vault

What is Data Vault?

Data Vault is a data warehouse modeling methodology designed to build an enterprise data platform that is:

  • scalable
  • auditable
  • historically accurate
  • adaptable to changing source systems
  • able to integrate data from many different sources

The central idea is: Separate business identity, relationships, and descriptive history.

Data Vault mainly does this through three building blocks:

Hub + Link + Satellite

You will often see this called: Hubs, Links, and Satellites — HLS

Hub: Represents the primary business objects.
Link: Defines the associations between business objects.
Satellite: Stores descriptive attributes and records historical changes to those attributes.

You can think this way, a Bank, it has many source systems

CRM
│
├── Customer
│
Core Banking
│
├── Customer
├── Account
│
Credit System
│
├── Customer
├── Loan
│
Credit Card System
│
├── Customer
├── Card
│
Mobile Banking
│
├── Customer
└── Transaction

A customer has:

CRM            → C123
Core Banking   → 100023
Credit System  → CU-789
Card System    → 998877

Assuming Source systems changeing

# Now

Customer
----------------
Customer_ID
Name
Email
Phone

# tomorrow changing to
Customer
----------------
Customer_ID
First_Name
Last_Name
Email
Mobile_Phone

Data Vault will sperate Business Identity and Descriptive Attributes

Hub_Customer, looks after “Customer Business Key”

Satellite_Customer: looks after

“Name
Email
Phone
Address
Status
… “

Once source add a new attribute – “Preferred_Language”, System only need change Satellite.

Assuming, Historical data tracking

assuming,

2024:
Customer = John
City = Toronto

2025:
Customer = John
City = Vancouver

2026:
Customer = John
City = Calgary

under this scenario, enterprice wants to know:
“When did John live in Toronto? When did he move to Vancouver, and when did he move to Calgary?”

Data Vault does “Don’t overwrite histrory unnecessarily“, keep all history change

Hub Customer
     │
     ▼
Satellite Customer
     
Customer_Key | City       | Load_Date
-------------|------------|-----------
C10001       | Toronto    | 2024-01-01
C10001       | Vancouver  | 2025-03-15
C10001       | Calgary    | 2026-02-10

Auditability

Enterprice offen asks where does this data come?
e.g. Customer Email = abc@gmail.com

Data Vault highly emphasizes:

  • Source System
  • Load Date
  • Record Source
  • Business Key
  • Hash Key
  • Hash Diff

so we create

Source System
↓
Raw Data
↓
Data Vault
↓
Business Data

to keep data lineage / history

The three core components of Data Vault

Hub – Link – Satellite

Hub represents a unique business concept or business key. It answers “Who is this business entity?”

Link represents a business relationship between Hubs. It is Business relationship

Customer owns Account
Customer applies for Loan
Customer holds Credit Card
Employee works for Branch
Supplier provides Product

Satellite stores descriptive attributes and their historical changes. It descripts Hub or Link and information lineages, history change.

example: customer

Data Vault will ask:
What is this entity? —- Hub

Customer ID = C10001

How does it relate to other objects? —- Link

Link Customer-Account
Customer C10001
|
| owns
|
Account A90001

What attributes does this Customer have? —- Satellite

Customer C10001

Name = John Smith
Email = john@gmail.com
City = Toronto
Status = Active

             ┌──────────────┐
             │    HUB       │
             │              │
             │ WHO ARE YOU? │
             └──────┬───────┘
                    │
                    │ relationship
                    ▼
             ┌──────────────┐
             │    LINK      │
             │              │
             │ WHO IS       │
             │ RELATED TO   │
             │ WHOM?        │
             └──────┬───────┘
                    │
                    ▼
             ┌──────────────┐
             │  SATELLITE   │
             │              │
             │ WHAT DO WE   │
             │ KNOW ABOUT   │
             │ IT?          │
             └──────────────┘

A complete example

assuming:

Customer:
C001

Account:
A100

Customer open Account.

Hub Customer

Hub_Customer

Customer_HK | Customer_BK | Load_DTS  | Record_Source
-------------|-------------|-----------|--------------
...          | C001        | 2026-01-01| CRM

Hub Account

Hub_Account

Account_HK | Account_BK | Load_DTS  | Record_Source
-----------|------------|-----------|--------------
...        | A100       | 2026-01-01| CoreBanking

Link Customer Account

Link_Customer_Account

Customer_HK | Account_HK | Load_DTS  | Record_Source
------------|------------|-----------|--------------
...         | ...        | 2026-01-01| CoreBanking

Satellite Customer

Sat_Customer

Customer_HK
Name
Email
City
Status
Load_DTS
Record_Source
Hash_Diff

e.g.

C001 | John | john@gmail.com | Toronto | Active

then:

C001 | John | john@gmail.com | Vancouver | Active

we keep the histrory

Key Takeaways

A scalable, auditable, historical data modeling methodology for enterprise data integration.

ComponentEnglish中文人话
HubBusiness identity / key业务身份你是谁
LinkBusiness relationship业务关系你和谁有关系
SatelliteDescriptive attributes + history属性 + 历史关于你的信息

Hub tells us WHAT business entity we have, Link tells us HOW entities are related, and Satellite tells us WHAT we know about those entities over time.

Hub 告诉我们“业务对象是谁”,Link 告诉我们“对象之间是什么关系”,Satellite 告诉我们“关于这些对象知道什么,以及这些信息如何随时间变化”。

Canonical Model

1. What is Canonical Model?

A canonical model is a design pattern that uses a single, standardized data format to let different systems talk to each other without needing custom connections for every pair.

先用人话说, Canonical Model = 企业统一规定的一套“标准数据语言”。大家都必须按照同一套“词典 + 语法 + 数据结构”来描述同一个业务对象。

For example, a company has 10 data system, each system has “customer”. but the “customer” is named in different way in each data system.

SystemCustomer IDCustomer NamePhone
CRMcustomer_idcustomer_namephone
BankingCUST_NOCUST_NMMOBILE_NO
Insuranceclient_idfull_nametelephone
ERPBP_IDBP_NAMETEL_NO

Canonical Model means that all “Customer” will be uniformly defined in this way in the future in the company.

Customer
│
├── customer_id
├── first_name
├── last_name
├── date_of_birth
├── email
├── phone
└── address

CRM.customer_id → Canonical.Customer.customer_id

Bank.CUST_NO → Canonical.Customer.customer_id

Insurance.client_id → Canonical.Customer.customer_id

So, this is the core concept of Canonical. Reduce data integration complexity through a common enterprise representation.

2. What are included in Canonical Model

2.1 Business Entity

How many important business entity are there in a enterprice? for eaxample, Banking:

  • Customer
  • Account
  • Product
  • Transaction
  • Branch
  • Employee
  • Loan
  • Payment

Healthcare:

  • Patient
  • Provider
  • Hospital
  • Encounter
  • Diagnosis
  • Medication
  • LabResult
  • ClinicalDocument

These are Canonical Business Entities

2.2 Attributes

Each entity has its own attributes. for example,

customer, it may define:

  • customer_id
  • first_name
  • last_name
  • date_of_birth
  • email
  • phone
  • status

2.3 Relationships

Canonical Model has to define relationship between entity, e.g. a customer may has multiple account, so it is ” 1 … N “

Customer
│
├── Account 001
├── Account 002
└── Account 003

3. Data Type

Data types are another key component of the Canonical Model. For example, ‘Date of Birth’ must have a consistent data type across the system, rather than being defined as a Varchar one day and a Date type the next.

4. Business Meaning

Unique enterprise identifier representing a customer within the enterprise customer domain.

How does Canonical Model use in Lakehouse

                ┌──────────────┐
                │ CRM          │
                └──────┬───────┘
                       │
                ┌──────▼───────┐
                │ Banking      │
                └──────┬───────┘
                       │
                ┌──────▼───────┐
                │ ERP          │
                └──────┬───────┘
                       │
                       ▼
              ┌──────────────────┐
              │ Bronze           │
              │ Raw Source Data  │
              └────────┬─────────┘
                       │
                       ▼
              ┌──────────────────┐
              │ Standardization │
              │ + Mapping       │
              └────────┬─────────┘
                       │
                       ▼
              ┌──────────────────┐
              │ Canonical       │
              │ Customer        │
              └────────┬─────────┘
                       │
                       ▼
              ┌──────────────────┐
              │ Unified /        │
              │ Conformed Data   │
              └────────┬─────────┘
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
       Power BI       ML/AI       APIs

A complete example

Assuming, a enterprice has 3 system CRM, Banking and Insurance

CRM

CUST_ID
CUST_NAME
MOBILE_NO

Banking

CUSTOMER_NO
FULL_NAME
PHONE

Insurance

CLIENT_ID
NAME
TELEPHONE

Canonical:

Customer
├── customer_id
├── name
└── phone

Mapping:

CRM.CUST_ID    --->    Customer.customer_id

CRM.CUST_NAME  --->    Customer.name

CRM.MOBILE_NO  --->    Customer.phone

Bank:

Banking.CUSTOMER_NO
        ↓
Customer.customer_id

Insurance:

Insurance.CLIENT_ID
        ↓
Customer.customer_id

Implementation in Databricks Lakehouse

Assuming data looks this:

CUST_ID | CUST_NM       | MOBILE_NO
--------|---------------|------------
10001   | John Smith    | 416-111-1111
10002   | Mary Chen     | 416-222-2222

Bronze:

-- ============================================================
-- BRONZE LAYER
-- Purpose:
--   保留 CRM 原始数据。
--
-- Key point:
--   Bronze 不应该急着改变 Source System 的原始结构。
--   我们首先保证 source fidelity / traceability。
-- ============================================================

CREATE TABLE bronze.crm_customer
AS
SELECT
    *
FROM source.crm_customer;

Create Canonical Customer

Customer

-- ============================================================
-- SILVER / CANONICAL LAYER
--
-- Purpose:
--   将 CRM Source Model 转换成 Enterprise Canonical Model。
--
-- 这里最重要的工作不是简单 rename column。
-- 我们同时执行:
--
--   1. Schema standardization
--   2. Data type standardization
--   3. Business naming standardization
--   4. Data cleansing
--   5. Business rule application
-- ============================================================

CREATE OR REPLACE TABLE silver.canonical_customer AS

SELECT
    -- --------------------------------------------------------
    -- 将 CRM 的 CUST_ID 映射为企业统一的 customer_id。
    -- --------------------------------------------------------
    CAST(CUST_ID AS STRING) AS customer_id,

    -- --------------------------------------------------------
    -- 将 Source System 的 CUST_NM
    -- 转换成 Canonical Model 的 name。
    -- trim() 用于消除 source data 中多余空格。
    -- --------------------------------------------------------
    TRIM(CUST_NM) AS name,

    -- --------------------------------------------------------
    -- 统一电话号码格式。
    -- 实际企业项目中这里可能还会调用标准化规则。
    -- --------------------------------------------------------
    MOBILE_NO AS phone,

    -- --------------------------------------------------------
    -- 添加 source_system。
    -- 这个 metadata 非常重要:
    -- 后续可以追溯数据来自哪个系统。
    -- --------------------------------------------------------
    'CRM' AS source_system,

    -- --------------------------------------------------------
    -- 记录 ingestion / transformation 时间。
    -- 用于 lineage、audit 和 troubleshooting。
    -- --------------------------------------------------------
    current_timestamp() AS processed_at

FROM bronze.crm_customer;

result:
customer_id | name        | phone
------------|-------------|---------------
10001       | John Smith  | 416-111-1111
10002       | Mary Chen   | 416-222-2222

Banking

  • CUSTOMER_NO
  • FULL_NAME
  • PHONE
-- ============================================================
-- 将 Banking Source Model 转换为同一个 Canonical Model。
--
-- 注意:
-- CRM 和 Banking 的 Source Schema 完全不同。
-- 但是输出 Schema 必须遵循同一个 Canonical Contract。
-- ============================================================

SELECT
    CAST(CUSTOMER_NO AS STRING) AS customer_id,
    TRIM(FULL_NAME) AS name,
    PHONE AS phone,
    'BANKING' AS source_system,
    current_timestamp() AS processed_at
FROM bronze.banking_customer;

Unified Customer

-- ============================================================
-- UNIFIED CUSTOMER
--
-- Purpose:
--   将不同 source systems 的 canonical records
--   汇聚成企业统一 Customer view。
--
-- 注意:
--   Canonicalization != Entity Resolution
--
--   Canonicalization:
--       把结构统一。
--
--   Entity Resolution:
--       判断两个 records 是否代表同一个真实 Customer。
-- ============================================================

CREATE OR REPLACE TABLE gold.unified_customer AS

SELECT
    customer_id,
    name,
    phone
FROM silver.canonical_customer_crm

UNION ALL

SELECT
    customer_id,
    name,
    phone
FROM silver.canonical_customer_banking;

Usually, in actual enterprise deployments, this stage usually includes the following additions:

Identity Resolution
Deduplication
Survivorship Rules
Master Data
Golden Record

Key Takeaways

Canonical Model answers: “When different systems talk about the same business concept, what should that concept look like and mean inside the enterprise?”

要点ENCN
Canonical ModelStandardized enterprise representation of data企业统一的数据表示方式
核心目的Reduce integration complexity降低系统集成复杂度
核心思想Different sources → common representation不同 Source → 统一表示
Business EntityCustomer, Account, Product, etc.业务实体
AttributesStandardized fields and meanings标准化字段及业务含义
RelationshipsDefines relationships between entities定义实体之间关系
Business SemanticsDefines what each field actually means定义字段真正的业务含义
Source ModelRepresents one source system某个源系统自己的数据模型
Canonical ModelEnterprise-standard representation企业标准数据模型
Data UnificationCombines data into a consistent enterprise view将数据统一成企业级一致视图
Master DataIdentifies and manages trusted business entities管理可信的核心业务实体
Data ContractDefines producer/consumer data agreement定义生产者与消费者之间的数据契约
DatabricksCanonical can be implemented using Delta tables/views可用 Delta Table/View 等实现
Canonical ≠ UnificationStructure standardization is not identity resolution结构统一不等于实体统一
Canonical + MetadataMetadata explains meaning, ownership, lineage, etc.Metadata 解释含义、Owner、Lineage 等
Canonical + DQCanonical fields can have standardized quality rulesCanonical 字段可以有统一质量规则
Canonical + GovernanceStandard model enables consistent governance标准模型有利于统一治理

Skill Composition: Multi-Skill Collaboration + MCP Composition

Today we move to Multi-Skill Collaboration + MCP Composition, which is important because it connects the Skill design capabilities with MCP Tools / Servers.

What does “Skill Composition” mean?

什么叫 Skill Composition?Composition means combining multiple specialized capabilities to accomplish a larger task.
Composition 的意思就是把多个专业能力组合起来完成一个更大的任务。

Multi-Skill = an Agent has multiple specialized capabilities and selects and combines them according to the task.
Multi-Skill = 一个 Agent 拥有多个专业能力,并根据任务选择和组合这些能力。

We have already learned that a Skill is a capability/work procedure, while an MCP Tool is an external execution capability.
Skill 是“能力/工作方法”,MCP Tool 是“外部执行能力”。

This topic addresses a practical problem: a real Agent cannot realistically have only one capability or call only one Tool.
一个真实 Agent 不可能只会一种能力,也不可能只调用一个 Tool。

MCP Composition = Skills do not implement every external operation themselves; they obtain external capabilities such as databases, APIs, filesystems, and Databricks through MCP.
MCP Composition = Skill 不直接实现所有外部操作,而是通过 MCP 获得数据库、API、文件系统、Databricks 等外部能力。

So, we can simply think this:

Skill = “我知道怎么做”
Skill = “I know how to do it.”

MCP Tool = “我有办法真正去做”
MCP Tool = “I have the ability to actually do it.”

LLM/Agent = “我决定什么时候做什么”
LLM/Agent = “I decide what to do and when to do it.”

ConceptEnglish中文
SkillDefines how to accomplish a capability定义如何完成一个能力
ToolDefines an executable operation定义一个可以执行的操作
AgentDecides what to do决定做什么
MCPProvides standardized access to external capabilities提供标准化的外部能力访问方式

Think about it like a human organization.
可以把它想象成人类组织。

Agent = Manager
Agent = 经理

Skill = Expert’s operating procedure
Skill = 专家的工作方法

Tool = Equipment the expert can use
Tool = 专家可以使用的工具

MCP = Standard interface connecting the expert to external equipment
MCP = 连接专家与外部工具的标准接口

Imagine that you have an AI Data Engineering Team, there are three experts in this team. They are: SQL Developer, Data Quality Engineer and Pipeline Auditor.

CapabilitiesSkillMCP
SQL DeveloperSQL Generation SkillDatabricks MCP
Data Quality EngineerDQ Report SkillDatabricks MCP
Pipeline AuditorPipeline Audit SkillDatabricks MCP

The team receiced this “Inspect the sales_orders pipeline, identify data-quality issues, generate SQL validations, and give me a report.”
“检查 sales_orders pipeline,找出数据质量问题,生成 SQL 验证,并给我一份报告。”

Agent may work this way:
User Request
│
▼
Agent
│
├── Pipeline Audit Skill
│ │
│ └── MCP → Databricks
│
├── SQL Generator Skill
│ │
│ └── MCP → Databricks
│
└── DQ Report Skill
│
└── MCP → Databricks

The Agent does not put all logic into one giant Skill. Instead, each Skill focuses on one clearly defined responsibility.
Agent 不是把所有逻辑塞进一个巨大 Skill。而是让每个 Skill 专注一个明确的职责。

Who should coordinate the Skills?

谁负责协调这些 Skill?Normally, Skill A should not directly control Skill B. Instead, the Agent or Orchestrator should coordinate them.
通常情况下,Skill A 不应该直接控制 Skill B。相反,应该由 Agent 或 Orchestrator 来协调它们。

             Agent
               │
   ┌───────────┼───────────┐
   │           │           │
   ▼           ▼           ▼
Skill A     Skill B     Skill C

The Agent decides which capability is needed. The Skill provides the instructions and workflow for that capability. This gives us a clean separation of responsibilities.
Agent 决定需要哪个能力。Skill 提供该能力所需要的 Instructions 和 Workflow。这样就实现了清晰的职责分离。

Skill Composition 的核心思想

Why we need Multi-Skill? A Skill can eventually contain thousands of lines of instructions. The LLM may have to process a large amount of information unrelated to the current task. This reduces focus, maintainability, and reliability.

First principle: One Skill should have one coherent capability
第一个原则:一个 Skill 应该负责一个内聚的能力

A Skill should have a clear purpose and a reasonably narrow scope.
一个 Skill 应该有明确的目的和相对清晰的职责范围。

A simple MCP Tool

from mcp.server.fastmcp import FastMCP


# Create the MCP Server.
# 创建 MCP Server。
#
# The MCP Server exposes external capabilities to an Agent.
# MCP Server 把外部系统能力暴露给 Agent。
mcp = FastMCP("Databricks MCP Server")


@mcp.tool()
def execute_sql(sql: str) -> str:
    """
    Execute SQL in Databricks.
    在 Databricks 中执行 SQL。
    
    This function represents an executable operation.
    这个函数代表一个可以执行的操作。
    """

    # In a real implementation, this section would connect
    # to Databricks and execute the SQL statement.
    # 在真实实现中,这里会连接 Databricks 并执行 SQL。

    # We return a simulated result for learning purposes.
    # 为了教学目的,这里返回一个模拟结果。
    return f"SQL executed: {sql}"


@mcp.tool()
def get_table_schema(table_name: str) -> str:
    """
    Retrieve table schema.
    获取表结构。
    """

    # In a real system, this would query Databricks metadata.
    # 在真实系统中,这里应该查询 Databricks metadata。
    return (
        f"Schema for {table_name}: "
        "customer_id, order_id, order_date, amount"
    )


# Start the MCP server.
# 启动 MCP Server。
if __name__ == "__main__":
    mcp.run()

Skill Testing and Iteration

This section is very important. provious we focused on how to write a Skill. we need answer a very simple question: is the Skill you wrote actually good?

Now, we focuse on how to prove whether the Skill works well and how to iteratively improve it. This is where we start moving from Prompt/Skill authoring into AI Engineering quality engineering.

For example, the SOP (Standard Operating Procedure) says:

Data Quality Report Skill

  1. Check row count.
  2. Check NULL values.
  3. Check duplicate records.
  4. Check data quality rules.
  5. Generate a report.

How do you know this SOP is actually good? You should test it.

A Test Case is basically an “exam question.” we have customer_id
1001
1002
1003
1003
<– duplicated
1004
NULL <– null

Now let agent use the Skill to Test.

Actual Result:
Row Count = 6
NULL = YES
Duplicate = YES

so result PASS.


if Agent return
Row Count = 6
NULL = NO
Duplicate = YES

so result FAIL

We should perpare multiple test case, because a Skill can encounter many different situations.

Key Takeaways

要点ENCN
Skill TestingTest whether a Skill behaves correctly测试 Skill 是否按照要求正确工作
Behavioral TestingValidate what the Skill actually does验证 Skill 实际执行了什么
Quality EvaluationEvaluate output quality, not just existence评价输出质量,而不仅仅是有没有输出
Golden Test CasesUse stable cases to validate behavior使用稳定的标准案例验证行为
Regression TestingMake sure old behavior does not break确保修改后原来的功能没有坏
Root Cause AnalysisIdentify which layer caused the failure判断失败到底是哪一层造成的
IterationTest → diagnose → modify → retest测试 → 定位 → 修改 → 再测试
AI EngineeringTreat Skill quality as an engineering problem把 Skill 质量当成工程问题处理
E07 vs G02E07 tests a Skill; G02 systematizes Golden Dataset evaluationE07 测试具体 Skill;G02 系统化构建 Golden Dataset