Exercise 4 · Design Contract-First
Design a derived data product before writing a single line of SQL.
Design a derived data product before writing a single line of SQL.
The purchasing team wants to know how often each SKU is bought per year, to negotiate better deals with suppliers. You build them a new data product on top of Orders: SKU Sales.
This time you work contract-first: you design the data contract and the data product description before writing any SQL. The contract is the specification. You implement it in the next exercise.
Create a new contract and open it in the editor. Confirm when the CLI asks to create the file.
datacontract edit sku_sales_per_year.odcs.yamlFile 'sku_sales_per_year.odcs.yaml' does not exist. Initialize a new data contract? [y/N]: y
📄 data contract written to sku_sales_per_year.odcs.yaml
Editing: /Users/you/learn.datacontract.com/sku_sales_per_year.odcs.yaml
Data Contract Editor running at http://localhost:4243
Press Ctrl+C to stopSet the fundamentals:
| Field | Value |
|---|---|
| Name | SKU Sales per Year |
| ID | sku_sales_per_year |
| Version | 1.0.0 |
| Status | draft |
The view will live in a new schema analytics in the same database. Add a Server:
| Field | Value |
|---|---|
| Server | postgres |
| Type | postgres |
| Host | localhost |
| Port | 5433 |
| Database | workshop |
| Schema | analytics |
Add a Schema sku_sales_per_year, set Advanced Metadata → Physical Type to VIEW, and add these properties:
| Property | Logical Type | Physical Type | Meaning |
|---|---|---|---|
sku | string | TEXT | The product SKU |
year | integer | INTEGER | Year of the order |
order_count | integer | BIGINT | How many orders contained the SKU |
total_quantity | integer | BIGINT | Total units bought |
Put the meaning into each property's Description. That is what the purchasing team reads.
The schema says which columns exist. Quality checks say what must be true about them. Add checks such as:
sku and year is uniquetotal_quantity is never less than order_countUse "count the bad rows, expect zero", as in Exercise 1:
quality:
- type: sql
description: Ensure total_quantity is at least order_count
query: SELECT COUNT(*) FROM analytics.sku_sales_per_year WHERE total_quantity < order_count;
mustBe: 0ODCS 3.2 lets you declare the role a property plays with semanticType.
In the editor, open a property in Schemas and set Semantic Type and Transform Logic, or edit the YAML:
properties:
- name: sku
semanticType: dimension
- name: year
semanticType: dimension
- name: order_count
semanticType: measure
transformLogic: COUNT(*)
- name: total_quantity
semanticType: measure
transformLogic: SUM(line_items.quantity)Analysts will ask AI assistants about this data product. Tell them how to use it with a context block (new in ODCS 3.2, see Exercise 1).
In the editor, open Context in the navigation:
context:
instructions: >-
One row per SKU and year. Sum order_count or total_quantity across years for totals.
verifiedStatements:
- question: Which three SKUs sold the most units in 2024?
answer: SELECT sku, total_quantity FROM analytics.sku_sales_per_year WHERE year = 2024 ORDER BY total_quantity DESC LIMIT 3;The verified statement is a question with a known-good answer. You will check it once the view exists.
Save and run the tests:
datacontract test sku_sales_per_year.odcs.yamlTesting sku_sales_per_year.odcs.yaml
Server: postgres (type=postgres, host=localhost, port=5433, database=workshop, schema=analytics)
╭────────┬────────────────────────────────────┬────────────────┬───────────────────────────────────╮
│ Result │ Check │ Field │ Details │
├────────┼────────────────────────────────────┼────────────────┼───────────────────────────────────┤
│ failed │ Ensure the view has data │ │ Could not read model │
│ │ │ │ 'sku_sales_per_year': │
│ │ │ │ sku_sales_per_year │
│ failed │ Check that field 'order_count' is │ order_count │ Could not read model │
│ │ present │ │ 'sku_sales_per_year': │
│ │ │ │ sku_sales_per_year │
…
╰────────┴────────────────────────────────────┴────────────────┴───────────────────────────────────╯
🔴 data contract is invalid, found the following errors:
1) 15 checks on sku, year, order_count, total_quantity: Could not read model 'sku_sales_per_year':
sku_sales_per_yearThe tests fail, of course: nothing is implemented yet. That is the point. The purchasing team can review the interface while you turn the tests green in the next exercise.
Create sku_sales_per_year.odps.yaml with the same structure as in Exercise 3:
| Field | Value |
|---|---|
id | sku_sales |
name | SKU Sales |
version | 1.0.0 |
status | draft |
domain | ecommerce |
Add a description with purpose, plus team and support for the purchasing analytics team (e.g. purchasing_analytics_team).
The product offers one output port, described by your new contract:
outputPorts:
- name: sku_sales_per_year
description: Aggregated SKU sales per year as a PostgreSQL view
version: 1.0.0
contractId: sku_sales_per_yearDeclare which data (and which guarantees) your product builds on. You consume orders_v2, because only v2 has quantity:
inputPorts:
- name: orders
version: 2.0.0
contractId: orders_v2Validate against the ODPS standard:
dataproduct lint sku_sales_per_year.odps.yaml✅ Data product is valid against ODPS v1.1.0
🟢 Data product is valid.