Data Contract CLIの技術検証及び導入を見送った理由の整理
はじめに
社内でData Contractの導入を検討しており、Data Contract CLIを利用できないか?と考えていました。
結論として社内ではCLIの採用は見送りましたが、技術検証の過程で得られた知見は参考になる部分もあるかと思います。
本記事ではCLIの技術検証で気になった内容と、CLIの世界観について自分なりに理解した内容をまとめます。
Data Contractの導入は運用フローやルールづくりも踏まえて整理が必要と感じており、その辺りの要件整理については別記事でまとめます。
AIの進化が著しいので、本記事でまとめている内容は調査しやすくなっていると思います。
技術調査の結果と所感が参考になれば幸いです。
注意点
Data Contractとは何か、といった前提情報やCLIに関する紹介は、既にまとめていただいた記事がありますので本記事では言及しません。
- Data Contract事例共有会 - Media List - connpass
- 15,000カラムの機密性を守る - CTOと実践したデータガバナンスの実現 - CADDi Tech Blog
- soonraah - Data Contract CLI から考える Data Contracts ファーストのデータパイプラインの未来
前提情報
- Data Contract CLIのバージョンは0.10.35です。
本記事執筆時点で、最新版は0.12.2です。
よって既に解消している内容に言及しているケースがあり得ますのでご注意ください。 - BigQuery及びdbt coreを社内で採用しています。
exportコマンドの検証内容を後述しますが、上記都合でBigQueryとdbt のみを対象にしているのでご注意ください。
導入を見送った理由
冒頭で記述した通りData Contract CLIの採用を一旦見送りました。
理由としては、SSoT(Single Source of Truth:唯一の信頼できる情報源)を考慮した情報管理がCLI単体の標準機能では難しい、と感じたためです。
特にCLIを利用する場合はexport機能は必須で使いたいと考えており、その検証をしていた際に強く感じた部分でした。
記事全体が文字ばかりなので、画像で何か理解の助けになったら幸いです。

Data Contract CLIを運用する場合に想定される運用・開発フロー

上記をベースにすると分かりやすいかと思いますが、簡単に以下の流れが想定されます。
initやlint, diffコマンドなどもあるのですが、そちらは割愛します。
- importしたいファイルの取り込み
- Contract内容に沿ったtest内容の反映
- testコマンドの実行・yml修正
- exportコマンドの実行
Contractの合意内容は機械的に反映できると良い
反映方法次第ですが、SSoTを考慮するとContract内容に沿ったtest内容の反映のステップはフローは可能であれば自動化したいと考えていました。
なぜならばData Contract自体は契約なので、硬い運用が適していると考えているためです。
事前にContract内容を合意できたにも関わらず、手動反映でミスが起きると非常に勿体無いです。
他社事例
実際にこのあたり他社さんではどのように対応しているか事前に簡単に調査した結果をまとめます。
私の想像が含まれている部分もあり、もしかしたら間違っているかもしれませんので、その点はご注意ください。
-
Data Contract CLIでデータテストを行い、dbtのschemaを生成する CI/CD
データオーナー側で先んじてymlを記載し、データ基盤側でレビューするフローになっています。
つまりCLIのフォーマットに沿ったymlを人間が作成するため、importは利用しないフローになっていそうです。 -
15,000カラムの機密性を守る - CTOと実践したデータガバナンスの実現 - CADDi Tech Blog
データガバナンス委員会で合意したものをスプレッドシートで管理し、 Data Contract CLIのImporterクラスを利用してymlを生成する流れになっています。
またTerraform向けにJsonファイルを出力するために、exportの部分はカスタマイズされています。 -
タイミーにおけるデータの利用シーンと データ基盤の挑戦
そもそもCLIは採用してないと思いますが、BQとスプシの内容を組み合わせてコントラクトを作成することで、dbt側でテストできるymlを作成するフローになっているかと思います。
なぜexport機能を必須で使いたいと考えていたか
Data Contract CLIにもtestコマンドは存在しています。
import機能を使う等してymlファイルを作成した後は、ymlの構文や実データに対する整合性のチェックも実は可能です。
ただし日々の運用で、CLI側のtestコマンドに依存するのは避けたいと考えていました。
Contractの内容次第ですが、厳しいケースではテストが失敗した場合、後続のモデル自体を実行したくない、というケースが考えられます。
イメージが近かったためメルカリさんの事例から拝借しますが、リンク先にある条件分岐の箇所です。
肥大化したデータ基盤の刷新 ‐安定と利便を分離するBasic Tables Coreの設計‐|Mercari Analytics Blog
dbtを採用している都合上、Data Contractで合意した内容をdbt のパイプラインの中でテストしたい、と考えていました。
これを担保するには exportコマンドの利用が必須になります。
なぜならば、Data Contract CLIで扱えるymlとdbtで扱えるymlは書き方が異なるためです。
そのためSSoTを考慮してこの機能が備わっていると、個人的には理解しています。
ただし、ここ機能の中でいくつか気になる点がありました。
Data Contract CLIで扱える内容
export機能の懸念点を理解するには、CLIが扱えるオブジェクトの全体像を把握する必要があります。
以下の表はCLIで扱える主要オブジェクトと、dbtのymlファイルへのexport可否をまとめたものです。
CLIはdbtの世界観で扱えるものだけexportすればよい、という棲み分けになっていそうだなと理解しています。
exportされるものはinfoとmodelsオブジェクトのみであるため、以下ではこの2つに集中して検証内容をまとめます。
| 要素 | 説明・補足 | dbtのymlにexport可能か |
|---|---|---|
| dataContractSpecification | データコントラクト仕様のバージョン定義 | ❌ |
| id | データコントラクトの一意なID | ❌ |
| info | データコントラクトのメタ情報(タイトル、バージョン、説明など)。ownerなどはテーブルに対するmetaタグとして反映されるため△としました。 |
△ |
| servers | データがどこに存在するか(例: S3, BigQuery) | ❌ |
| terms | 利用規約(データの使用方法に関する制限や利用期間、請求に関する情報) | ❌ |
| models | データの構造(スキーマ)を定義します。テーブルやカラム、データ型など。exportできないものもあるため△としました | △ |
| definitions | データの型や最大・最小値、PIIなど。models側のテストで担保できるものも混じっている | ❌ |
| servicelevels | データ品質に関する目標や、データの鮮度、可用性などのサービスレベルを定義 | ❌ |
infoオブジェクトで管理できるもの
Contractを管理する上で重要になるのはowner やcontact になるかなと思います。
| Field | Type | Description |
|---|---|---|
title |
string | 必須. データコントラクトのタイトル。 |
version |
string | 必須. データコントラクトドキュメントのバージョン。 |
status |
string | データコントラクトのステータス。proposed, in development, active, deprecated, retired のいずれか。 |
description |
string | データコントラクトの説明。 |
owner |
string | データコントラクトとデータ提供を管理する責任者またはチーム。 |
contact |
Contact Object | データコントラクトの連絡先情報。exportの対象外。 |
上記のうちcontact の詳細はさらに以下の情報を管理できますが、exportの対象外です。
| Field | Type | Description |
|---|---|---|
name |
string | 連絡担当者または組織の識別名。 |
url |
string | 連絡先情報へのURL。必須でURL形式であること。 |
email |
string | 連絡担当者または組織のメールアドレス。必須でメールアドレス形式であること。 |
export前のCLIのフォーマットに対応したサンプルファイル
dataContractSpecification: 1.2.0
id: data-contract-sample
info:
title: Data Contractのサンプル
version: 1.0.0
description: |
Data Contractに関するdescriptionを記述する
owner: プロダクトチーム
status: active
contact:
name: "@product_team"
models:
data_contract_test_table:
type: table
export後のdbtに対応したymlファイル
version: 2
models:
- name: data_contract_test_table
config:
meta:
data_contract: data-contract-sample
owner: プロダクトチーム
materialized: table
contract:
enforced: true
通知方法が事前に合意できていると良い
Data Contractの運用上、仮に合意した内容が失敗した場合、関係者に通知したくなると思います。
社内ではdbt coreを採用していますが、dbt単体には通知する機能はありません。
また上述した通り、CLIの思想としてdbtの世界観で扱えるものだけexportできれば良い、と考えられていると思います。
よって uv run datacontract export hogehoge.yml --format dbt 等を実行したとしても、contactの内容はmeta情報としてもexportされません。
社内ではPrefectを利用しているのと、slackのやりとりがメインです。
そのためPrefect側でエラーハンドリング予定です。
modelsオブジェクトで管理できるもの
Contractを管理する上で重要になるのは、カラム単位のテストを記述する fields になるかなと思います。
| フィールド名 | 型 | 説明 | dbtのymlにexport可能か |
|---|---|---|---|
| type | string | モデルの型。例: table, view, object。デフォルトは table。 |
✅ |
| description | string | データモデルに関する任意の説明。 | ✅ |
| title | string | データモデルのタイトル。モデル名が分かりにくい場合に特に便利です。 | ✅ |
| fields | Map[string, Field Object] | データモデルのフィールド(例: カラム)。struct構造など複雑なものは対象外。詳細は後述のfieldsオブジェクトを確認ください | △ |
| primaryKey | Array of string | 複合主キーの場合、主キーを構成するフィールド名のリスト。フィールドレベルの primaryKey の代替です。私の方でcommitして対応してもらうようにしました PR #898
|
✅ |
| quality | Array of Quality Object | モデルレベルでの品質属性を指定します。モデルレベルでテストするのはelementary(dbtのデータ品質テストツール)を利用した統計的なテストケースや、dbt_expectationsを利用した少し複雑なテストになるかと思います。カラムレベルのテストはfields側で記述可能です。 | ❌ |
| examples | Array of Any | モデルのサンプルデータセットを指定します。 | ❌ |
| additionalFields | Boolean | モデルがコントラクトで定義されていない追加フィールドを持つことができるかを指定します。デフォルトは false。 |
❌ |
| config | Config Object | 追加のツールで役立つ可能性のある、その他のキーと値のペア。 | ❌ |
fieldsオブジェクトで管理できるもの
| フィールド名 | 型 | 説明 | exportの対象か |
|---|---|---|---|
| description | string | このフィールドのデータの意味に関する任意の説明。 | ✅ |
| type | Data Type | フィールドの論理データ型。 | ✅ |
| title | string | フィールドの人間が読める名前。フィールド名が分かりにくい場合に特に便利です。 | ✅ |
| enum | array of string | 値がこの配列の要素のいずれかと等しくなければなりません。値がnullでない場合のみ評価されます。 | ✅ |
| required | boolean | このフィールドが値を必須とし、nullであってはならないかを示します。デフォルトはfalse。 |
✅ |
| primaryKey | boolean | このフィールドが主キーであるかを示します。デフォルトはfalse。 |
✅ |
| references | string | 他のモデルのフィールドへの参照。例:orders.order_id。外部キー関係を定義します。 |
✅ |
| unique | boolean | 値がモデル内で一意でなければならないかを示します。デフォルトはfalse。 |
✅ |
| format | string |
email,uri,uuidなどの書式を指定します。 |
❌ |
| precision | number | 数値の最大桁数。数値にのみ適用されます。デフォルトは38。 | ❌ |
| scale | number | 数値の小数点以下の最大桁数。数値にのみ適用されます。デフォルトは0。 | ❌ |
| minLength | number | 値はこれ以上でなければなりません。文字列型にのみ適用されます。 | ✅ |
| maxLength | number | 値はこれ以下でなければなりません。文字列型にのみ適用されます。 | ✅ |
| pattern | string | 値がECMA-262の正規表現に準拠している必要があります。文字列型にのみ適用されます。 | ❌ |
| minimum | number | 数値がこの値以上である必要があります。 | ✅ |
| exclusiveMinimum | number | minimumを判定する際の例外データとしてのminをセットする | ❌ |
| maximum | number | 数値がこの値以下である必要があります。 | ✅ |
| exclusiveMaximum | number | maximumを判定する際の例外データとしてのmaxをセットする | ❌ |
| examples | Array of Any | 値の例のリスト。 | ❌ |
| pii | boolean | 個人を特定できる情報(PII)を含むかを示します。metaタグとして反映される | ✅ |
| classification | string | 組織の分類体系に応じた、このフィールドの機密性レベルを定義するデータクラス。metaタグとして反映される | ✅ |
| tags | Array of string | 追加のコンテキストを提供するためのカスタムメタデータ。 | ✅ |
| links | Map[string,string] | 追加の外部リンク。 | ❌ |
| $ref | string | 仕様内の定義への参照URI。プロパティは定義から継承されます。 | ✅ |
| fields | Map[string, Field Object] |
object,record,struct型の場合のネストされたフィールド。親のカラムのみ反映可能で、配下のカラムはexportされません。よって、struct構造配下のカラムに対するテストも管理できないです。 |
△ |
| items | Field Object |
array型の場合の配列要素の型。 |
❌ |
| keys | Field Object |
map型の場合のキーの構造を記述します。 |
❌ |
| values | Field Object |
map型の場合の値の構造を記述します。 |
❌ |
| quality | Array of Quality Object | フィールドレベルでの品質属性を指定します。 | ❌ |
| lineage | Lineage Object | データの出所に関する情報を提供します。 | ❌ |
| config | Config Object | 追加のツールで役立つ可能性のある、その他のキーと値のペア。 | ❌ |
export前のCLIのフォーマットに対応したサンプルファイル
以下のように記述すると、CLI側のフォーマットに沿った記載形式になります。
dataContractSpecification: 1.2.0
id: data-contract-sample
info:
title: Data Contractのサンプル
version: 1.0.0
description: |
Data Contractに関するdescriptionを記述する
owner: プロダクトチーム
status: active
contact:
name: "@product_team"
models:
data_contract_test_table:
type: table
primaryKey: ["order_id", "user_id"]
fields:
order_id:
type: string
required: true
user_id:
type: string
products:
type: array
items:
type: struct
fields:
product_id:
type: string
product_name:
type: string
create_date:
type: date
export後のdbtに対応したymlファイル
productsのみがexportされ、配下のproduct_idなどは反映されません。
version: 2
models:
- name: data_contract_test_table
config:
meta:
data_contract: data-contract-sample
owner: プロダクトチーム
materialized: table
contract:
enforced: true
data_tests:
- dbt_utils.unique_combination_of_columns:
combination_of_columns:
- order_id
- user_id
columns:
- name: order_id
data_type: STRING
constraints:
- type: not_null
- name: user_id
data_type: STRING
constraints:
- type: not_null
- name: products
data_type: ARRAY
テストに関する制約事項
STRUCT構造のカラムに対するテストはdbtのgeneric testでは対応できない
BigQueryでは、複数のフィールドを一つのカラムにまとめる「STRUCT構造」を使用できます。
例えば、注文データで商品情報(商品ID、商品名、日付など)を配列として格納する場合などに使われます。
現状、社内で上記のカラムに対するテストはカスタムマクロを用意し、以下のように対応しています。
Writing custom generic tests
- name: products
data_type: >-
ARRAY<STRUCT<product_id STRING, product_name STRING, create_date DATE>>
- name: products.create_date
data_type: DATE
data_tests:
- custom_macros:
test: not_null
当時はこの点を失念してissueとして起票したのですが、カスタムマクロの名称が組織によって異なる都合上、CLIの標準機能としてSTRUCT構造に対するテストを設けることは難しいかもしれません。
(issue起票時は間違えてrecord型で検証していたのでその点は目を瞑ってください..)
社内ではデータソースとしてSTRUCT構造でデータをBQ上に取り込むデータが多く存在しており、この点が少々厳しいなと感じていました。
elementaryのような複雑なテストには対応していない
10Xさんの記事でも言及されていますが、初めから複雑なテストに対応する必要はそもそもないと思います。
とはいえ将来の拡張性を考えた際に、懸念点になるかもと検証時に考えていました。
10XにおけるData Contractの導入について
fieldsオブジェクトに記載できるテストからも何となく分かりますが、dbtのgeneric data tests以外にもテストを記述することができます。
ソースコードを読んでいただくのが一番早いですが、dbt_utilsとdbt_expectationsには対応しています。
ただしelementaryには対応していません。
またdbt_expectationsのうちexpect_table_aggregation_to_equal_other_table のような複雑なテストをするものも対応していません。
よって、上記のような条件が複雑なテストをContractの対象に含めたい場合、CLI側を改修するなり自分たちでカスタマイズする必要があります。
この辺りはData Contractの社内ポリシーを先に定めておくと良さそう、と個人的には感じています。
dbtのフォルダ構成を事前に決めておくと良い
ポイントとしては、Data Contract用のymlファイルと既存のdbt用ymlファイルは分離して管理する、という点です。
export機能自体はSSoTを考慮している影響か、既存ファイルを上書きする仕様 になっています。
とはいえ、Data Contractの締結対象とするテーブルは一部に限定されるかと思います。
またdbtの仕様を考慮した際、複数のファイルに対して同じモデルを指定することはできません。
以上を踏まえて2点を考慮します。
- Data Contract用のファイルは別途管理する
- Data Contract用のymlからexportしたものと、既存のdbt用のymlは分けて管理する
具体例を示すために、以下のようにdbtフォルダ構成が分かれているとします。
ymlは各サブフォルダで定義しているものとします。
dbt_project/models/
│
├── source/ # dl_* 層
│ ├── dl_product_A/ # プロダクトA用のsourceフォルダ
│ │ ├── domein_X/ # ドメインごとのサブディレクトリ
│ │ ├── domein_Y/
│ │ └── ...
│ ├── dl_product_B/ # プロダクトB用のsourceフォルダ
│ └── ... # その他のデータソース
│
├── staging/ # dwh_* 層
│ ├── dwh_product_A/ # プロダクトA用のstgフォルダ
│ │ ├── domein_X/ # ドメインごとのサブディレクトリ
│ │ ├── domein_Y/
│ │ └── ...
│ ├── dwh_product_B/ # プロダクトB用のstgフォルダ
│ │ ├── domein_X/ # ドメインごとのサブディレクトリ
│ │ ├── domein_Y/
│ │ └── ...
│ └── ... # その他のプロダクト用のフォルダ
│
├── intermediate/ # int_* / dwh_* (中間層)
│ ├── dwh_product_A_agg/ # プロダクトA用に再利用性が高いモデルを集約したもの
│ └── ...
│
└── marts/ # dm_* 層
├── dm_product_A_agg/ # プロダクトA用のデータマート
└── ...
仮にstg層でContractを結ぶとした場合、以下のようなイメージで考えていました。
他にもData Contractの元ファイル自体を各ドメインのフォルダ配下に置く方法もありますし、datacontractの元ファイルは一つにまとめてexportする際に工夫するなど、他にもやり方は考えられるかと思います。
dbt_project/models/staging/dwh_product_A/domein_X/
├── stg_domein_X.yml
└── stg_domein_X_contract.yml(仮 datacontractCLIでexportしたファイル)
dbt_project/datacontract/(仮 datacontractの元ファイルを管理するフォルダ)
├── stg_domein_X_contract_sources.yml(仮 datacontractの元ファイル)
└── ...
まとめ
技術検証の結論
本記事ではData Contract CLIの技術的な面に着目して検証を行いました。
- CLIのimport機能単体では、Data Contractで合意したテストを全て反映することは難しい
- CLIのexport機能はSSoTを意識した設計になっている(exportが上書き、diffで差分チェック可能など)
- ただし、STRUCT構造のカラムに対するテストやelementaryへの対応が不足
- exportした際に情報が失われるものがある
- 社内要件を満たすにはカスタマイズが必要と判断し、採用を見送り
私の方でもCLI側に修正PRを作成するなど試みましたが、使いやすくするためにはまだ改善の余地がありそうです。
とはいえ、Data Contractはデータ基盤だけで運用するものではなく、多様なimport/export形式に対応している点など、コンセプトや方向性は素晴らしいと感じています。
今後の方針
所属している会社ではData Contractの運用を段階的に開始する方針にしました。
本記事ではCLIの技術検証に焦点を当てましたが、実際の要件整理や段階リリースについては別記事でまとめる予定です。
Discussion
貴重な情報を公開していただきありがとうございます。私も Data Contract CLI を含む Data Cotract について調査していたので、具体的な内容が記載されておりとても参考になりました。
また、記事内容ではないのですが、導入を見送った理由にて画像で説明されているアイディアがとても素晴らしいです。ブログ記事を書いていて文章だけだと伝わりにくいなと思ったときに、私も画像を添付しようと思いました。
感想ありがとうございます、とても嬉しいです。
タイミング的に自分も記事を書いて良かったなと思いました。
私はNotebook LMを使いましたが、GeminiのNano Bananaなどでも画像生成できると思うので、その点も参考になれば幸いです。
Data Contract自体はデータ基盤だけでは進まないですが、ちょっと概念であったり運用の流れがテキストだけだと難しいと思うので..
社内の方に説明する際も同様に画像生成したのですが、直感的に課題が伝わった感じがして個人的にも助かったなと思いましたね。