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.
面向特定业务领域、部门或分析需求的数据分析存储层。
| Concept | What is it? | Main Purpose |
|---|---|---|
| Data Lake | Large-scale flexible data repository | Store diverse data |
| Data Warehouse | Integrated analytical platform | Enterprise analytics |
| Data Mart | Domain-oriented analytical store | Business-domain analytics |
| Lakehouse | Architecture combining lake + warehouse capabilities | Unified 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:
- Select the Business Process
- Declare the Grain
- Identify the Dimensions
- 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:
- Transaction Fact grain (事务粒度)
- Periodic snapshot Fact grain (周期快照粒度)
- Accumulating snapshot Fact grain (累积快照粒度)
| Fact Type | Grain | 核心问题 |
|---|---|---|
| Transaction Fact | One business event 一次业务事件 | What happened? 发生了什么? |
| Periodic Snapshot | One entity/process at a periodic point 某个周期时点的状态 | What was the state? 当时状态如何? |
| Accumulating Snapshot | One process lifecycle 一个业务流程生命周期 | How did the process progress? 流程进行到哪里? |
Transaction Fact Grain
One row represents one business transaction/event.
e.g. One row = one order line
| Date | Customer | Product | Store | Qty | Sales |
|---|---|---|---|---|---|
| Sep 13 | C001 | P001 | S001 | 1 | 1200 |
| Sep 13 | C001 | P002 | S001 | 2 | 500 |
| Sep 13 | C001 | P003 | S001 | 1 | 50 |
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
| Date | Account | Balance |
|---|---|---|
| Sep 11 | A001 | 10,000 |
| Sep 12 | A001 | 9,500 |
| Sep 13 | A001 | 11,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.
| Order | Order Date | Payment Date | Ship Date | Delivery Date |
|---|---|---|---|---|
| 10001 | Sep 10 | Sep 10 | Sep 11 | Sep 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
| Order | Product | Quantity | Sales Amount |
|---|---|---|---|
| O1001 | iPhone | 1 | $1,200 |
| O1001 | AirPods | 2 | $500 |
| O1002 | MacBook | 1 | $2,000 |
A error fact example.
| Order | Product | Sales Amount | Order Total |
|---|---|---|---|
| O1001 | iPhone | 1,200 | 1,700 |
| O1001 | AirPods | 500 | 1,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
| Order | Customer | Order Total | Shipping Cost |
|---|---|---|---|
| O1001 | C001 | 1700 | 20 |
| O1002 | C002 | 2000 | 30 |
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?
| Model | Question | 中文 |
|---|---|---|
| Conceptual | What? | 有什么业务东西? |
| Logical | How is it organized? | 数据逻辑上怎么组织? |
| Physical | How 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:
- It is already in 1NF 首先必须满足 1NF
- 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:
- It is in 2NF
- 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
- Data Modeling translates the real business world into structured data.
是把现实业务世界转换成结构化的数据。 - An Entity represents a business object or concept.
表示一个业务对象或业务概念。 - An Attribute describes an Entity.
描述 Entity 的特征 - A Relationship describes how Entities are connected.
描述不同 Entity 之间如何关联。 - 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
| Concept | Professional Meaning | 人话 |
|---|---|---|
| Pipeline | Application / execution boundary | 整个工厂 |
| Dataset | Data object produced/maintained | 产品 |
| Flow | Data movement / processing path | 传送带 |
| Dependency | Relationship between datasets | 谁必须等谁 |
| Pipeline Engine | Executes 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
| Column | Meaning |
|---|---|
| customer_id | Customer identifier |
| name | Customer name |
| Customer email | |
| status | ACTIVE / INACTIVE |
| country | Customer country |
| updated_at | Source/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_ID | Status | Action | Destination | |
|---|---|---|---|---|
| Valid | Valid | ACTIVE | ACCEPT | Silver |
| Valid | Valid | INACTIVE | ACCEPT | Silver |
| NULL | Valid | Valid | QUARANTINE | Quarantine |
| Valid | NULL | Valid | QUARANTINE | Quarantine |
| Valid | Valid | Invalid | QUARANTINE | Quarantine |
| NULL | NULL | Valid | QUARANTINE | Quarantine |
| NULL | Valid | Invalid | QUARANTINE | Quarantine |
| Valid | NULL | Invalid | QUARANTINE | Quarantine |
| NULL | NULL | Invalid | DROP | Drop 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"
)
)
三个时间的区别 (以后你面试很可能会被问到)
| Field | Meaning |
|---|---|
updated_at | Source business timestamp |
_source_file_modification_time | Source file timestamp |
_ingest_ts | Platform 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_id | status | _source_file | _ingest_ts | |
|---|---|---|---|---|
| C001 | alice@example.com | ACTIVE | customers_001.json | … |
| C002 | bob@example.com | ACTIVE | customers_001.json | … |
| C003 | charlie@example.com | INACTIVE | customers_001.json | … |
| C004 | david@example.com | ACTIVE | customers_002.json | … |
| C005 | emma@example.com | ACTIVE | customers_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_id | status | _dq_action | 去哪里 | |
|---|---|---|---|---|
| valid | valid | ACTIVE | ACCEPT | Silver |
| valid | valid | INACTIVE | ACCEPT | Silver |
| NULL | valid | valid | QUARANTINE | Quarantine |
| valid | NULL | valid | QUARANTINE | Quarantine |
| valid | invalid format | valid | QUARANTINE | Quarantine |
| valid | valid | invalid | QUARANTINE | Quarantine |
| NULL | NULL | valid | QUARANTINE | Quarantine |
| NULL | valid | invalid | QUARANTINE | Quarantine |
| valid | NULL | invalid | QUARANTINE | Quarantine |
| NULL | NULL | invalid | DROP | Drop 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 Table | Materialized View | |
|---|---|---|
| 典型用途 | incremental row processing | derived / aggregated result |
| 数据特点 | 持续到来的数据 | 查询计算结果 |
| 常见操作 | filter / transform / enrich | aggregation / business metrics |
| API | @dp.table | @dp.materialized_view |
| 我们的例子 | customer_silver | customer_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.
| Component | English | 中文 | 人话 |
|---|---|---|---|
| Hub | Business identity / key | 业务身份 | 你是谁 |
| Link | Business relationship | 业务关系 | 你和谁有关系 |
| Satellite | Descriptive 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.
| System | Customer ID | Customer Name | Phone |
|---|---|---|---|
| CRM | customer_id | customer_name | phone |
| Banking | CUST_NO | CUST_NM | MOBILE_NO |
| Insurance | client_id | full_name | telephone |
| ERP | BP_ID | BP_NAME | TEL_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
- 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?”
| 要点 | EN | CN |
|---|---|---|
| Canonical Model | Standardized enterprise representation of data | 企业统一的数据表示方式 |
| 核心目的 | Reduce integration complexity | 降低系统集成复杂度 |
| 核心思想 | Different sources → common representation | 不同 Source → 统一表示 |
| Business Entity | Customer, Account, Product, etc. | 业务实体 |
| Attributes | Standardized fields and meanings | 标准化字段及业务含义 |
| Relationships | Defines relationships between entities | 定义实体之间关系 |
| Business Semantics | Defines what each field actually means | 定义字段真正的业务含义 |
| Source Model | Represents one source system | 某个源系统自己的数据模型 |
| Canonical Model | Enterprise-standard representation | 企业标准数据模型 |
| Data Unification | Combines data into a consistent enterprise view | 将数据统一成企业级一致视图 |
| Master Data | Identifies and manages trusted business entities | 管理可信的核心业务实体 |
| Data Contract | Defines producer/consumer data agreement | 定义生产者与消费者之间的数据契约 |
| Databricks | Canonical can be implemented using Delta tables/views | 可用 Delta Table/View 等实现 |
| Canonical ≠ Unification | Structure standardization is not identity resolution | 结构统一不等于实体统一 |
| Canonical + Metadata | Metadata explains meaning, ownership, lineage, etc. | Metadata 解释含义、Owner、Lineage 等 |
| Canonical + DQ | Canonical fields can have standardized quality rules | Canonical 字段可以有统一质量规则 |
| Canonical + Governance | Standard 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.”
| Concept | English | 中文 |
|---|---|---|
| Skill | Defines how to accomplish a capability | 定义如何完成一个能力 |
| Tool | Defines an executable operation | 定义一个可以执行的操作 |
| Agent | Decides what to do | 决定做什么 |
| MCP | Provides 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.
| Capabilities | Skill | MCP |
|---|---|---|
| SQL Developer | SQL Generation Skill | Databricks MCP |
| Data Quality Engineer | DQ Report Skill | Databricks MCP |
| Pipeline Auditor | Pipeline Audit Skill | Databricks 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
- Check row count.
- Check NULL values.
- Check duplicate records.
- Check data quality rules.
- 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
| 要点 | EN | CN |
|---|---|---|
| Skill Testing | Test whether a Skill behaves correctly | 测试 Skill 是否按照要求正确工作 |
| Behavioral Testing | Validate what the Skill actually does | 验证 Skill 实际执行了什么 |
| Quality Evaluation | Evaluate output quality, not just existence | 评价输出质量,而不仅仅是有没有输出 |
| Golden Test Cases | Use stable cases to validate behavior | 使用稳定的标准案例验证行为 |
| Regression Testing | Make sure old behavior does not break | 确保修改后原来的功能没有坏 |
| Root Cause Analysis | Identify which layer caused the failure | 判断失败到底是哪一层造成的 |
| Iteration | Test → diagnose → modify → retest | 测试 → 定位 → 修改 → 再测试 |
| AI Engineering | Treat Skill quality as an engineering problem | 把 Skill 质量当成工程问题处理 |
| E07 vs G02 | E07 tests a Skill; G02 systematizes Golden Dataset evaluation | E07 测试具体 Skill;G02 系统化构建 Golden Dataset |

