【dbt Best Practice】How we structure our dbt projects 2024年12月版
ハッピバースデートゥミ〜〜〜♪ハッピバースデートゥミ〜〜〜♪、ハッピバースデーディアわたし〜♪
ハッピバースデートゥミ〜〜〜♪
がく@ちゅらデータ(50)です。
ついに天命を知ることになりました。天命ナニソレオイシイノ?
昨年は、【dbt Best Practice】改めて、dbt Style Guide を読んでみる
という記事を投稿しました。
今年は、また別のBest Practiceについて読んでみようと思いました。
この記事は、dbtアドベントカレンダー2024の12日目の記事です。
How we structure our dbt projects(dbtプロジェクトの構造)
dbtプロジェクトの構造がなぜ重要なのか
人間が協働するためには、「一貫性があり理解しやすい規範を確立すること」が極めて重要
ファイル命名規則を考えたり、ルーチンワークは極力配して、よりクリエイティブな仕事・難しい問題の解決に注力することができるはず
※はず・・・よねぇ
dbtの大きな目的としては、
「ソースデータから ビジネスデータへ変換していく」
こと。
- ソースデータは、コントロールできない外部システムによって生成される。
- ビジネスデータは、もらったデータから、検討し、作成したニーズ、コンセプト、定義に変換する。
このガイドは、2019年5月にClaire Carrollさんによる記事の更新版
2019年に書かれたものですが、その後いろいろな環境変化があって、一部変更されているけども、基本的な部分は変わっていない。今後も新しいツールや洞察によって視点が強化されていくので、このガイドは今後も繰り返し更新されていくはず
学習
このガイドの目的は主に3つ
- 典型的なdbtプロジェクトの構成方法の最新推奨事項を網羅
- 上記の推奨事項を例をあげて解説する
- 各段階で、なぜそのアプローチを推奨するか?の説明をして、組織でカスタマイズする際にどこをやれるのか?あたりを判断できるようにする
このガイドを読めば、dbtプロジェクトの構成要素がどうなっているか?深く理解して、アナリティクスエンジニアリングの目的と原則がより明確かつ直感的に理解できるようになる(はず)
ガイドの構成
データ変換の流れと同じ順序で、トピックを追っていくマス。
- modelsディレクトリにある3つの主要なレイヤーのファイル、フォルダ、モデルをどのように構築するのかを詳しく見てみる
- Staging(ステージング) :ソースデータから取り込み(RAW)層を作成
- Intermediate(中間) :クレンジングで型を整えたり、マートを作るための整形とかする
- Marts(マート) :モジュール化した(断片化)モデルを組織で判断に使えるようになモデルにまとめる
- これらのレイヤーがプロジェクトの他の部分にどのようにフィットするかを探る
1.全体的な構造を包括的に見直す
2.YAML構成の詳細を
3.dbtプロジェクトでの他のフォルダの使い方について説明 :tests
,seeds
,analyses
プロジェクトを構成のツリーはだいたいこんな感じ
多すぎるので、ここではざっと捉えておいて、後で一つずつ焦点を当てて説明する
dbt projectのフォルダ構成
jaffle_shop
├── README.md
├── analyses
├── seeds
│ └── employees.csv
├── dbt_project.yml
├── macros
└── cents_to_dollars.sql
├── models
│ ├── intermediate
││ └──finance
│├──_int_finance__models.yml
││ └──int_payments_pivoted_to_orders.sql
│ ├── marts
│├──finance
│ │ ├── _finance__models.yml
││ ├──orders.sql
│ │ └── payments.sql
│└──marketing
│├──_marketing__models.yml
│└──customers.sql
│ ├── staging
│├──jaffle_shop
│ ├── _jaffle_shop__docs.md
│ │ ├── _jaffle_shop__models.yml
│ ├── _jaffle_shop__sources.yml
│ │ ├── base
│ ├── base_jaffle_shop__customers.sql
│ │ └── base_jaffle_shop__deleted_customers.sql
│ │ ├── stg_jaffle_shop__customers.sql
│ │ └──stg_jaffle_shop__orders.sql
│└──stripe
│ ├──_stripe__models.yml
│├──_stripe__sources.yml
│└──stg_stripe__payments.sql
│ └── utilities
│ └── all_dates.sql
├── packages.yml
├── snapshots
└── tests
└── assert_positive_value_for_total_amount.sql
Staging: Preparing our atomic building blocks
ステージング層は、
- dbtにおいて、データの起点となる層
- 他のシステムからデータを取り込んで、その後の変換する。(変換は中間層やマート層が担う)
Staging: Files and folders
例とするStagingのフォルダ構成はこちら
例とするStagingのフォルダ構成
models/staging
├── jaffle_shop
│ ├── _jaffle_shop__docs.md
│ ├── _jaffle_shop__models.yml
│ ├── _jaffle_shop__sources.yml
│ ├── base
│ │ ├── base_jaffle_shop__customers.sql
│ │ └── base_jaffle_shop__deleted_customers.sql
│ ├── stg_jaffle_shop__customers.sql
│ └── stg_jaffle_shop__orders.sql
└── stripe
├── _stripe__models.yml
├── _stripe__sources.yml
└── stg_stripe__payments.sql
フォルダ
フォルダは非常に重要。
- ◯ ソースシステムに基づくサブフォルダ。この例だと、stripe(請求系)とjaffle_shopのデータを分けていて、どこから来たソースなのかが一目瞭然
- ✗ データを取り込むツールごとのサブフォルダはNG。(Fivetran/ とか TROCCO/ とか)
- ✗ ビジネスグループに基づくサブフォルダ。(マーケティング/ とか 財務/ とか)
- そもそも、マーケティングとか財務とかは、マート層など実際使うところで分ける
- 早く分けすぎるとヘタな重複とかが発生して、SSOT(Single Source of Truth)が実現できない
ファイル名
- dbtでは一貫したファイル名のパターンを作成することが重要
- dbtにおいてファイルは一意
- ウェアハウス製品(SnowflakeとかBigQuery)で作られるモデル名(テーブル名やビュー名)に対応している必要あり
- 変換に関する具体的な情報など、できるだけ多くの明確な情報をファイル名に入れるのがオススメ
- ◯ stg_[source]__[entity]s.sql
- __を使うことで、より区切り位置が明確になるので
- stg_stripe__payments.sql とか stg_jaffle_shop__orders.sql
- ◯ stg_[source]__[entity]s.sql
- ✗ stg_[entity].sql
- 最初、モデルが少ない間はいいかもしれないけど、プロジェクトが成長して、モデルの数が増えてくるとファイル名でその素性を見ただけでわからなくなる
- ファイル名にソースシステムを追加すると見つけやすくなる
- ◯ 複数形にする
- SQLはできる限り「散文」のように読める必要がある
- 訳注)散文(Prose)とは「〔装飾の少ない〕事実伝達の[無味乾燥な]文」という意味がマッチしそう。
- テーブルには複数のレコーがあるので、それを表すテーブル名は、複数形がただしいという(ちょっと哲学入ってますね)
Staging: Models ステージング層の(dbt)モデル
※ここで言うモデルは、機械学習などの文脈のモデルとは違うことに注意
基本的には、ステージング層のモデルはすべて同じパターンになる
stg_stripe__payments.sql
-- stg_stripe__payments.sql
with
source as (
select * from {{ source('stripe','payment') }}
),
renamed as (
select
-- ids
id as payment_id,
orderid as order_id,
-- strings
paymentmethod as payment_method,
case
when payment_method in ('stripe', 'paypal', 'credit_card', 'gift_card') then 'credit'
else 'cash'
end as payment_type,
status,
-- numerics
amount as amount_cents,
amount / 100.0 as amount,
-- booleans
case
when status = 'successful' then true
else false
end as is_completed_payment,
-- dates
date_trunc('day', created) as created_date,
-- timestamps
created::timestamp_ltz as created_at
from source
)
select * from renamed
※この形は dbtプロジェクトのスタイルのBest Practiceでも言及されてる
- CTEで記載
- sourceってimportを記載する部分がある
- {{ source('stripe','payment') }} というマクロを使って、データを取り込む。dbt的にはデータの起点を読み込む
- renamed で
- ◯ 名前の変更:カラム名を整える
- ◯ 型キャスト:日時型など型を適切なものへ変換する
- ◯ 基本的な計算:セントからドルなど、JOINをしない単純なもの
- ◯ 分類:case式などで値を整形する、クレンジングする
やってはいけないことは
- ✗ JOIN:ステージングの目的は、ここのデータソース由来のデータを綺麗にして、下流で使用するため。
- ✗ 集計:ステージングではグループ化は行わない。
◯ ビューとしてマテリアライズ(実体化)する
ステージング層は最終的な成果物ではなく、下流のモデルの構築ブロックとなることを意図している。
- ステージング層のモデルを参照する下流モデル(martsで詳しく説明)は、常に、集めて具体化するすべてのコンポーネントビューから可能な限り最新のデータを取得する
- データ消費者によるクエリを意図していないモデルにウェアハウス内のスペースを無駄に費やすことを回避し、そのため、それほど高速または効率的に実行する必要がない。
また、ステージング層のモデルはソースのテーブルと1対1の関係を保つ必要がある
下流モデルが、データソースのデータを扱うためのエントリーポイントのような役割を担う
Staging: Other considerations(ステージング:その他の考慮事項)
ステージングで結合が必要な場合はBaseモデルを作る
DRYを保つためにJOINが必要な場合は、サブディレクトリ「base」を作るのがオススメ
別の削除テーブルへの結合
→ 削除したものを別テーブルに移し替えたりする場合
下の例では、
-
base_jaffle_shop__customers.sql
-
base_jaffle_shop__deleted_customers.sql
の2つのsourceがある。同じ「customers」なテーブルだけど、削除フラグを付けた形でくっつけて出したい -
stg_jaffle_shop__customers.sql
- こちらで上記のbaseモデルをLEFT OUTER JOINして、削除フラグを付けている
base_jaffle_shop__customers.sql
-- base_jaffle_shop__customers.sql
with
source as (
select * from {{ source('jaffle_shop','customers') }}
),
customers as (
select
id as customer_id,
first_name,
last_name
from source
)
select * from customers
base_jaffle_shop__deleted_customers.sql
with
source as (
select * from {{ source('jaffle_shop','customer_deletes') }}
),
deleted_customers as (
select
id as customer_id,
deleted as deleted_at
from source
)
select * from deleted_customers
stg_jaffle_shop__customers.sql
-- stg_jaffle_shop__customers.sql
with
customers as (
select * from {{ ref('base_jaffle_shop__customers') }}
),
deleted_customers as (
select * from {{ ref('base_jaffle_shop__deleted_customers') }}
),
join_and_mark_deleted_customers as (
select
customers.*,
case
when deleted_customers.deleted_at is not null then true
else false
end as is_deleted
from customers
left join deleted_customers on customers.customer_id = deleted_customers.customer_id
)
select * from join_and_mark_deleted_customers
スキーマは同一だけど、別WHに配備されるようなテーブル
たとえば、Spotifyなどのデータは、ショップごとにデータソースが別だったりするけど、スキーマは同一・・・な場合
- base_shop_foo__customer.sql
- base_shop_bar__customer.sql
- ......
というのが会って
-- stg_shops__customer.sql
なテーブルで、unionとかjoinするとか(ショップ名を付与する形)
ステージングテーブルの生成を自動化する
- ステージングモデルを手動で書くのはいい習慣だし、理解するのはとてもいいこと
- ただし、ある程度理解したらあとは自動化しよう、codegenパッケージを使って
ユーティリティフォルダ
models/utilities
フォルダのしたに、分析に便利なデータを置いておいたりする
例えば
-
dbt_utilsのdate_spineマクロをつかった日付テーブル(日付をリスト化しておくもの)
- 欠損日とかを求めるのに使える
Intermediate: Purpose-built transformation steps ( っ中間層: 目的に合わせた構築する変換のステップ )
ステージング層の準備ができたら、次はより複雑で連結したモデルに変換していく。中間層はこんなモデルを置く場所。
Intermediate: Files and folders (中間層:ファイルとフォルダ)
中間層にあるモデルの例として
models/intermediate
└── finance
├── _int_finance__models.yml
└── int_payments_pivoted_to_orders.sql
フォルダ
- フォルダは、「ビジネスグループに基づくサブフォルダ」を構成
- フォルダ自体は、
models/intermediate
ファイル名
- **int_[entity]s_[verb]s.sql
- 中間層で行う処理の動詞を考える
- pivoted
- aggreagted_to_user
- joined
- fanned_out_by_quantity
- funnel_created
- SQLが分からなくてもそのモデルでどんな処理をするかが明示
- ファイル名は長くなってもいい(むしろ長いことを推奨している)
- 「__(アンダースコアが2つ)」は使わなくてもいい
- 中間層で行う処理の動詞を考える
Intermediate: Models ( 中間層:モデル )
中間モデルの目的は、マートモデルの複雑さを軽減すること
- モデルをDRYにするためにJinjaを少し使う場合もあり
- モデル内のCTEに適切な説明をつけるべし
- SQLがわからない関係者がそのセクションで何してるか分かるように
- モデル化するか、CTEでの記載で分けるか・・は難しいが、スケールの違いだけ
int_payments_pivoted_to_orders.sql
{%- set payment_methods = ['bank_transfer','credit_card','coupon','gift_card'] -%}
with
payments as (
select * from {{ ref('stg_stripe__payments') }}
),
pivot_and_aggregate_payments_to_order_grain as (
select
order_id,
{% for payment_method in payment_methods -%}
sum(
case
when payment_method = '{{ payment_method }}' and
status = 'success'
then amount
else 0
end
) as {{ payment_method }}_amount,
{%- endfor %}
sum(case when status = 'success' then amount end) as total_amount
from payments
group by 1
)
select * from pivot_and_aggregate_payments_to_order_grain
- ✗エンドユーザへの公開はNG
- この層はエンドユーザへは公開しない
- 一時的に実体化させる
- ephemeralを使って、モデル化(テーブルやビュー)をさせないで、一時的に作られる形の実装もあり
- 不要なモデルをWHに配置しなくていい
- が、トラブルシューティングはちょっと難しくなる(テーブルがないから)
- 特別な権限を持つカスタム スキーマのビューとして実現
- WH製品側でスキーマ(BigQueryならDataset)を分けると良い
- トラブルシューティングが楽になる
中間層のモデルの一般的な使用例は
- 構造の簡素化
- 適切な数(4〜6個)のモデルをまとめる、同じようなモデルを
- 再粒度化
- マートで結合する際に、中間層で粒度を揃えておく
- 複雑な操作の分離
- 複雑で理解しにくいロジックを中間層においておく
- 後続(下流)モデルが簡素化できる
:::messages
DAGを狭くしていく、テーブルは広がっていく
ステージング層(広い)からだんだんとマートに行くに従いまとめられていくイメージ
stg_1__hoge - int_1_geho - mart_1_gehogeho
stg_2__hoge
stg_3__hoge
stg_4__hoge - int_2_geho
stg_5__hoge
みたいな。
モデルへの複数の入力は許可するが、複数の出力は許可しないことです
複数の矢印が出てくるのは経験的に危険信号である場合が多い。
このルールを破る必要がある状況はあるけれども、慎重に検討して、可能なら避けるべき
:::
Marts: Business-defined entities (マート層:ビジネス定義エンティティ)
- マート層は、すべて(ステージング層のモデル、中間層のモデル)が一つにまとまるレイヤー
- 特定のエンティティまたは概念を独自の粒度で表す
- このため、マート層を、「エンティティ層、コンセプト層」と呼称することもある
- エンドユーザへの公開を目的とした層
Marts: Files and folders (マート層:ファイルとフォルダ)
models/marts
├── finance
│ ├── _finance__models.yml
│ ├── orders.sql
│ └── payments.sql
└── marketing
├── _marketing__models.yml
└── customers.sql
- 部門または関心領域別にグループ化
- この段階では部門 (マーケティング、財務など) 別にグループ化することが最も一般的な構造
- ただし、マート数が10個未満の場合は、サブフォルダに分ける必要は薄い
- エンティティによる名前つけ
- マートの粒度の概念に基づいて、平易な英語を使って命名する
- ✗ 同じコンセプトをチームごとに作る
-
finance_orders
やmarketing_orders
といったモデルは作らない - 例外はある
- 財務(finance)部門に特定のニーズがある場合等
- 同じ利益についても、会社で使うか政府に出すか?て異なるの
- tax_revenue(政府)、revenue(会社)とする
- finance_revenue(財務部門向け)、marketing_revenue ではない
-
Marts: Models (マート層:モデル)
今から挙げる例は、ビジネスに適合した=会社のビジョンとニーズに合わせたエンティティを実現
orders.sql
-- orders.sql
with
orders as (
select * from {{ ref('stg_jaffle_shop__orders' )}}
),
order_payments as (
select * from {{ ref('int_payments_pivoted_to_orders') }}
),
orders_and_order_payments_joined as (
select
orders.order_id,
orders.customer_id,
orders.order_date,
coalesce(order_payments.total_amount, 0) as amount,
coalesce(order_payments.gift_card_amount, 0) as gift_card_amount
from orders
left join order_payments on orders.order_id = order_payments.order_id
)
select * from orders_and_payments_joined
customers.sql
-- customers.sql
with
customers as (
select * from {{ ref('stg_jaffle_shop__customers')}}
),
orders as (
select * from {{ ref('orders')}}
),
customer_orders as (
select
customer_id,
min(order_date) as first_order_date,
max(order_date) as most_recent_order_date,
count(order_id) as number_of_orders,
sum(amount) as lifetime_value
from orders
group by 1
),
customers_and_customer_orders_joined as (
select
customers.customer_id,
customers.first_name,
customers.last_name,
customer_orders.first_order_date,
customer_orders.most_recent_order_date,
coalesce(customer_orders.number_of_orders, 0) as number_of_orders,
customer_orders.lifetime_value
from customers
left join customer_orders on customers.customer_id = customer_orders.customer_id
)
select * from customers_and_customer_orders_joined
- テーブルまたは増分モデル(increamental)として具体化(=materialized)する
- パフォーマンスが上がる
- 再計算のコストが節約
- 一般的に、materialize戦略は、まずビュー、時間がかかるならテーブル、テーブルでも時間がかかるなら増分(increamental)
- まずシンプル(=ビュー)に開始して、必要に応じて、複雑さ(テーブルや増分モデル)を追加していく
- ワイドテーブル、非正規化
- 現代のWH製品は、ストレージが安くコンピューティングが高価
- なのでワイドテーブル(大福帳)にしたり、非正規化にして、必要なものすべてを提供する
- ✗ 一つのマートに概念をまとめすぎるのはNG
- dbt 変換を構築する際の 1 つの良い経験則は、1 つのマートにあまり多くの概念をまとめないようにすること
- 内容が把握できないぐらい複雑ならモジュール化する
- 4つ以上の概念をまとめる場合は、中間層にモデルを追加すると効果的
- 別々のマートを慎重に構築する
- マートからマートを作るのはよい
- マートを後で別のマートを構築するために使用することは問題ない
- リソースの無駄や循環依存関係を回避するために慎重に検討する必要があることに注意
Marts: Other considerations(マート層:その他の考慮事項)
- トラブルシューティングはテーブルを使う
- 開発環境では、ephemeralとか使わないほうがいいかも。トラブルシューティングがめんどい
The dbt Semantic Layer and marts
- セマンティック レイヤーを使用している場合は、マートに対してより正規化されたアプローチをお勧め
- セマンティック レイヤーを使用していない場合は、dbt プロジェクトで一般的になっている、より非正規化されたアプローチをお勧め
セマンティック レイヤーの構造、命名、編成に関する推奨事項の完全なリストについては、別のベスプラドキュメントで説明している
※今回は、こちらは対象外
The rest of the project (プロジェクトの残り)
Project structure review
今までは、modelsディレクトリのお話が主にしてきたが、別のYAMLや他のファイル、フォルダについて説明する
プロジェクト全体
models
├── intermediate
│ └── finance
│ ├── _int_finance__models.yml
│ └── int_payments_pivoted_to_orders.sql
├── marts
│ ├── finance
│ │ ├── _finance__models.yml
│ │ ├── orders.sql
│ │ └── payments.sql
│ └── marketing
│ ├── _marketing__models.yml
│ └── customers.sql
├── staging
│ ├── jaffle_shop
│ │ ├── _jaffle_shop__docs.md
│ │ ├── _jaffle_shop__models.yml
│ │ ├── _jaffle_shop__sources.yml
│ │ ├── base
│ │ │ ├── base_jaffle_shop__customers.sql
│ │ │ └── base_jaffle_shop__deleted_customers.sql
│ │ ├── stg_jaffle_shop__customers.sql
│ │ └── stg_jaffle_shop__orders.sql
│ └── stripe
│ ├── _stripe__models.yml
│ ├── _stripe__sources.yml
│ └── stg_stripe__payments.sql
└── utilities
└── all_dates.sql
YAML in-depth
Yamlファイルをどう配置するか
- TOPレベルの dbt_project.ymlやpackages.ymlは特定の場所に必ず書く必要あり
- sourecsやmodelsのファイルは任意、自由に設置可能
推奨事項と一般的な代替案の長所と短所を記載する
- ◯ フォルダごとに作る
- フォルダ(jaffle_shopやstripe)事に作る
- ファイル名は、'_[directory]__models.yml' 例)_jaffle_shop__models.ymlや_jaffle_shop__sources.yml
- 先頭にアンダースコアを入れると分かりやすい
- ディレクトリを含めると探しやすくなる
- 命名規則についてはかなり変わってきている
- ドキュメントを作る場合は、_[directory]__docs.md 例)_jaffle_shop__docs.md
- ディレクトリごとに作るのがオススメ
- ✗ プロジェクトごとに設定
- sourcesとmodelsのYAMLをすべて一つにまとめることは非推奨
- 出来なくはないけど、見つけにくくなる
- 適切な粒度に分ける方が良い
- ! モデルごとに構成
- モデルごとにYAMLファイルを作る場合もある
- ファイルを探しやすい
- ただ、経験上、追加したり管理したりする開発がやや遅くなりがち
- ディレクトリごとに作るのがバランスが良い
- ◯ カスケード構成
- トップレベル(dbt_project.ymlとか)で大まかな設定
- ディレクトリを掘るごとにそこに特有の設定を追加
How we use the other folders(その他のフォルダ)
jaffle_shop
├── analyses
├── seeds
│ └── employees.csv
├── macros
│ ├── _macros.yml
│ └── cents_to_dollars.sql
├── snapshots
└── tests
└── assert_positive_value_for_total_amount.sql
- seeds
- ◯ モデリングには役立つけど、どのソースシステムにも存在しないルックアップテーブルを読み込むこと
- ✗ ソースシステムからWHにデータロードするのには使わない
- dbtはデータロードツールとしては設計されていない、餅は餅屋であるべき。できはするけど
- analyses
- ◯ 監査クエリ保存用
- クエリは保存できるけど、WH内に組み込むことは出来ない
- dbt Labsでは、監視ヘルパーパッケージをつかったクエリを保存するのに使っている
- このパッケージは、別のシステムから dbt にロジックを移行するときに出力の不一致を見つけるのに非常に役立つ
- ◯ 監査クエリ保存用
- tests
- ◯ 複数の特定のテーブルを同時にテストできる
- snapshots
- ◯ SCD TYPE2でスナップショットが取ることができる
- macros
- ◯ 繰り返し行われる変換をマクロ化して、DRY化できる
Project splitting(プロジェクトの分割について)
dbtプロジェクトが大きくなってくると、dbtを複数のプロジェクトに分けたくなるかもしれない
その際は、dbt Meshを使うことをオススメ
※dbt Meshは、dbt Cloudの機能
- ビジネスグループや部門で分割
- ビジネスグループで管理、開発したほうが開発のスピードが上がりそう
- データガバナンス
- 部門ごとに管理したほうが早いし、データの中身も一番精通してる。PIIとかも
- プロジェクトのサイズ
- モデルが数千とかになると、もう手に負えなくなりますよね
- その際は分けたほうがいいでしょう
- ✗ MLやBI
- レポーティングをしたり、予測をしたりするプロジェクトを分けるのは時期尚早ではないかとおもっていて、現時点では推奨していない
Final considerations(最後に)
- 全体的に、一貫性は何より重要と考えている。
- ほとんどのプロジェクトにこれまで記述したアプローチをオススメするが、組織ごとにも最適解は異なる
- ここで提示する唯一の独断的なアドバイスは、上記の構造の変更したい側面が見つかった場合は、その理由を真剣に考え、これらの規則からどのように、なぜ逸脱しているのかをチームに文書化すること
この文章は、現時点(2024年12月)のもので、今後もどんどん変わっていくでしょう
まとめ(私見)
久々にdbtプロジェクトに関するベスプラを読みましたが、結構変わってるなぁと感じました
セマンティックレイヤーについては、そのあるなしで、マートの構成推奨が変わる点などは驚きました
命名規則、フォルダ構成とかを決めておくとそこは機械的に出来て、より本質的なところに注力できるようになるなというのは、そのとおりだと思います
ただ、staging/stg_hogehoge__custumer などと、サブフォルダ名がモデル名に重複して入ってしまうことは、うーん、こっちのほうがいいのは何となく分かるがぁぁぁぁぁ
以前、質問があり、ここについては悩みましたが、自分的には、LOGをみてトラブルシューティングする際に、モデル名から配置場所などが特定できたので、自分の中では冗長な気もしますが、アリと思っています
昨年は、dbt Style Guideのベスプラを読みました。
今後も、dbtのベスプラを定期的に読んでいきたいと思います。結構生き物ですね!記載が変わってます!
Discussion