[ver1.5新機能]対象のモデルのデータ型や制約が定義通りであるか保証できる「Contracts」を試してみた

2023.05.23

さがらです。

先日、dbt-coreのver1.5がリリースされました。

dbt-core ver1.5の新機能として、「Contracts」がリリースされました。この機能を試してみたので、本記事で試した内容をまとめてみます。

Contractsとは

一言でいうと、対象のモデルのデータ型や制約が定義通りであるか保証できる機能です。

dbtは基本的に定義されたSELECT文が返す結果に応じて、カラム名とデータ型を自動で決めてくれますが、ソースデータの仕様変更などの影響でデータ型が勝手に変わってしまうことなどが起こり得ます。

勝手にデータ型が変わると、後続のdbtのモデルが上手く動かなくなったり、BIツールで対象のモデルから出力されたテーブルを参照するダッシュボードが壊れてしまったり、いろんなトラブルに繋がる可能性があります。

そんなときに役立つのが、今回の新機能「Contracts」です!

dbt上で事前に各モデルが出力するカラムに対してdata_typeconstraintsを定義しておくことで、データ型がちゃんと定義どおりか、制約も問題ないか(使用するDB・DWHで強制できるもの限定)、をdbt run/build実行時に確認できるようになります。

試してみた

検証環境

  • DWH:Snowflake
  • dbt Cloud
    • dbt-Core:ver1.5 ※Environmentより設定

検証内容

ある中間モデルintermediate_customers.sqlについて、出力されるテーブルのデータ型を保証するために、Contractsを定義してみます。

  • intermediate_customers.sql
select
    customer_id,
    first_name,
    last_name,
    first_order,
    number_of_orders
from
    {{ ref("int_customer_order_history_joined") }}

これを一度実行しSnowflake上で確認すると、下図のようにデータ型が定義されていました。

Contractsの定義

上述のintermediate_customers.sqlに対してyamlファイルintermediate_customers.ymlを作成し、以下のように定義します。

ポイントは3つあります。

  • contract:では、enforced: trueとすることで、後述のdata_typeconstraintsを満たしているか確認するようになる
  • data_type:では、使用しているDB・DWHのデータ型の名称を用いて、どのデータ型であることを満たさないといけないか、定義する
  • constraintsでは、使用しているDB・DWHの制約を用いて、どの制約を満たさないといけないか、定義する
    • ※注意事項:DWHの仕様上で強制できないものは強制できないため別途dbtのテスト機能を用いて保証する必要があります(例えば、Snowflakeではnot null以外は強制できない)。ただ、constraintsで定義された制約はDDL文に含まれるようになります。
models:
  - name: intermediate_customers
    config:
      contract:
        enforced: true

    columns:
      - name: customer_id
        data_type: varchar
        constraints:
          - type: not_null
          - type: primary_key
        tests:
          - unique
          - not_null

      - name: first_name
        data_type: varchar

      - name: last_name
        data_type: varchar

      - name: first_order
        data_type: date

      - name: number_of_orders
        data_type: number

constraintsを定義したことによる効果の確認

上述の内容でContractsを定義した上で、一度buildしてみます。

すると、build実行時に「SnowflakeではPrimary Keyは強制していないよ!」とWARNINGが表示されました。Snowflakeにおいてはconstraintsは強制できるnot null制約についてのみ、実際のデータを確認するようです。

実際にSnowflakeで作られたDDL文を見ると、constraintsで定義したprimary keynot nullがどちらもDDL文に含まれていることがわかります。Contractsでconstraintsを定義すれば、dbt_constraintsパッケージをインストールしなくても制約をdbtから適用できるので便利ですね!

あえてデータ型をContractsで定義したdata_typeと違うものにしてみる

続いて、data_typeがちゃんと機能しているのかを検証するため、あえてdata_typeと異なるデータ型を出力するようにモデルの定義を変更してみます。

具体的には、customer_iddata_type: varcharとしてますが、あえてCASTしてintとして出力されるようにしてみます。

select
    customer_id::int as customer_id,
    first_name,
    last_name,
    first_order,
    number_of_orders
from
    {{ ref("int_customer_order_history_joined") }}

この状態でbuildすると、下図のようにThis model has an enforced contract that failed. Please ensure the name, data_type, and number of columns in your contract match the columns in your model's definition.というエラーメッセージと、具体的にどのカラムにどういった問題があるのかをまとめた表が返ってきます。

このように事前にcontractsを定義しておくことで、data_typeが定義どおりでない場合はエラーを出力してくれることがわかりました!

最後に

dbt-coreのver1.5の新機能「contracts」を試してみました。

あるモデルから出力されるテーブルにおいてデータ型や制約を保証して、後続のモデルやBIツールなどに影響が出ないようにしたい、そんなときに使える機能だと思います!