🔏

Data Contract CLIの技術検証及び導入を見送った理由の整理

に公開
2026/01/09
2件

はじめに

社内でData Contractの導入を検討しており、Data Contract CLIを利用できないか?と考えていました。

結論として社内ではCLIの採用は見送りましたが、技術検証の過程で得られた知見は参考になる部分もあるかと思います。
本記事ではCLIの技術検証で気になった内容と、CLIの世界観について自分なりに理解した内容をまとめます。
Data Contractの導入は運用フローやルールづくりも踏まえて整理が必要と感じており、その辺りの要件整理については別記事でまとめます。

AIの進化が著しいので、本記事でまとめている内容は調査しやすくなっていると思います。
技術調査の結果と所感が参考になれば幸いです。

注意点

Data Contractとは何か、といった前提情報やCLIに関する紹介は、既にまとめていただいた記事がありますので本記事では言及しません。

前提情報

  • 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コマンドなどもあるのですが、そちらは割愛します。

  1. importしたいファイルの取り込み
  2. Contract内容に沿ったtest内容の反映
  3. testコマンドの実行・yml修正
  4. exportコマンドの実行

Contractの合意内容は機械的に反映できると良い

反映方法次第ですが、SSoTを考慮するとContract内容に沿ったtest内容の反映のステップはフローは可能であれば自動化したいと考えていました。
なぜならばData Contract自体は契約なので、硬い運用が適していると考えているためです。

事前にContract内容を合意できたにも関わらず、手動反映でミスが起きると非常に勿体無いです。

他社事例

実際にこのあたり他社さんではどのように対応しているか事前に簡単に調査した結果をまとめます。
私の想像が含まれている部分もあり、もしかしたら間違っているかもしれませんので、その点はご注意ください。

なぜ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すればよい、という棲み分けになっていそうだなと理解しています。

Data Contract Object

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型で検証していたのでその点は目を瞑ってください..)
https://github.com/datacontract/datacontract-cli/issues/599

社内ではデータソースとして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

manabianmanabian

貴重な情報を公開していただきありがとうございます。私も Data Contract CLI を含む Data Cotract について調査していたので、具体的な内容が記載されておりとても参考になりました。

また、記事内容ではないのですが、導入を見送った理由にて画像で説明されているアイディアがとても素晴らしいです。ブログ記事を書いていて文章だけだと伝わりにくいなと思ったときに、私も画像を添付しようと思いました。

1
sugatosugato

感想ありがとうございます、とても嬉しいです。
タイミング的に自分も記事を書いて良かったなと思いました。

私はNotebook LMを使いましたが、GeminiのNano Bananaなどでも画像生成できると思うので、その点も参考になれば幸いです。
Data Contract自体はデータ基盤だけでは進まないですが、ちょっと概念であったり運用の流れがテキストだけだと難しいと思うので..
社内の方に説明する際も同様に画像生成したのですが、直感的に課題が伝わった感じがして個人的にも助かったなと思いましたね。

1