dataform-engineering-fundamentals について
BigQuery を使う Dataform プロジェクトの開発・保守向けスキルです。SQLX の構成、依存関係とデータ品質の実践に加え、開発環境を優先するテスト駆動の手順を扱います。
主な機能
- 01階層化された SQLX 構成
- 02安全な開発実行と dry run
- 03ref() による依存関係管理
- 04増分テーブルのパターン
- 05アサーションとカラム文書化
- 06テスト駆動開発のワークフロー
活用シーン
- 01SQLX 変換の構築
- 02パイプライン障害の調査
- 03レポートテーブルとソースの作成
- 04デプロイ前のデータ品質検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ihistand/ claude-skills --skill dataform-engineering-fundamentalsインストール後、Claude Code で/dataform-engineering-fundamentalsを入力して読み込まれたか確認します。
詳細
Dataform エンジニアリングの基本
概要
基本原則: Dataform の開発において、安全対策と適切なアーキテクチャは決して省略できません。時間的なプレッシャーやビジネス上の緊急性があっても同じです。近道によって「節約」できる時間は、デバッグ、依存関係の破損、本番環境の問題に何時間も費やすことにつながります。
必須の基盤: このスキルは superpowers:test-driven-development を基盤としています。このスキルの TDD の原則はすべてここでも適用されます。このスキルでは、それらの原則を BigQuery の Dataform SQLX ファイル向けに適用します。
PostgreSQL/Supabase の場合: 代わりに sqlanvil-engineering-fundamentals を使用してください(または併用してください)。以下のアーキテクチャ、${ref()}、TDD、columns:{} のルールはそのまま適用されます。このスキルでは、Postgres/Supabase 固有の差分(config ブロック、認証情報、DDL、名前付き接続)を扱います。
公式ドキュメント: 構文/設定/API は https://cloud.google.com/dataform/docs 、リポジトリの構成と命名規則は https://cloud.google.com/dataform/docs/best-practices-repositories を参照してください。
使用するタイミング
Dataform に関するあらゆる作業でこのスキルを使用してください。新しい SQLX 変換、テーブルの変更、ソースの追加、パイプラインのトラブルシューティング、「手早く」作成するレポートやアドホック分析が対象です。
時間に追われているとき、関係者が待っているとき、遅くまで作業して疲れ切っているとき、あるいは「とにかく動くようにすればいい」と考えそうなときは、特に重要です。省略したくなる状況こそ、規律が最も重要になります。
関連スキル:
- superpowers:brainstorming — コードを書く 前に 大まかな要件を明確な設計にまとめます
- superpowers:systematic-debugging — パイプライン障害に対する体系的な問題解決
- superpowers:root-cause-tracing — 連鎖するエラーを発生源までたどります
- elements-of-style:writing-clearly-and-concisely — カラムの説明、コミットメッセージ、その他すべての文章に使用します
必須の安全対策
常に必要です。納期、緊急性、「単純な」作業を理由とした例外はありません。
1. テストには必ず --schema-suffix dev を使用する
# WRONG: testing in production
dataform run --actions my_table
# CORRECT: test in dev first
dataform run --schema-suffix dev --actions my_table
本番環境に触れることなくテストできるよう、schema_dev.my_table に書き込みます。
2. 実行前に必ず --dry-run を使用する
dataform compile # check compilation
dataform run --schema-suffix dev --dry-run --actions my_table # validate SQL, estimate cost
dataform run --schema-suffix dev --actions my_table # only then execute
BigQuery のスロットを使用する前に、SQL エラー、依存関係の欠落、想定外のコストを検出できます。
3. ref() より先にソースを宣言する
依存関係の追跡が機能するよう、外部テーブルをクエリする前に宣言してください。
-- definitions/sources/external_system/table_name.sqlx
config {
type: "declaration",
database: "project_id",
schema: "external_schema",
name: "table_name"
}
次に、FROM ${ref("table_name")} で参照します。FROM \project.external_schema.table_name`` は決して使用しないでください。
4. 必ず ${ref()} を使用する — テーブルパスをハードコードしない
-- WRONG
FROM `project.reporting_schema.customer_metrics`
SELECT * FROM project.source_schema.customers
-- CORRECT (after declaring the source)
FROM ${ref("customer_metrics")}
SELECT * FROM ${ref("customers")}
${ref()} は依存関係グラフを自動的に構築し、--schema-suffix を機能させ、リファクタリングの安全性を保ちます。ハードコードされたパスは、この3つすべてを損ないます。例外はありません。 SQLX ファイルでテーブルパスをハードコードする正当な理由はありません。
5. ref() の構文:デフォルトでは引数を1つ指定する
ソース宣言から Dataform がフルパスを解決するため、引数を1つ指定する ref() を優先してください。
FROM ${ref("sales_order")}
スキーマ間でテーブル名が重複していて曖昧さを解消する必要がある場合や、明示的にスキーマ/ソースを指定する必要がある場合(例:複数のデータセットに同じテーブル名がある ODS レイヤー)には、引数を2つ指定する ref() を使用してください。
FROM ${ref("external_schema", "sales_order")}
どちらも有効です。まずは引数を1つ指定する方法を使い、スキーマを明示する必要があるときには引数を2つ指定する方法を選びましょう。これはエラーではありません。
6. 基本的な検証クエリ
出力を必ず検証してください。空のテーブル、NULL、不適切な結合といった、すぐには気づきにくい問題を検出できます。
bq query --use_legacy_sql=false \
"SELECT COUNT(*) FROM \`project.schema_dev.my_table\`"
bq query --use_legacy_sql=false \
"SELECT COUNT(*) FROM \`project.schema_dev.my_table\` WHERE key_field IS NULL"
アーキテクチャパターン(必須)
「手早く」行う作業でも例外ではありません。(リポジトリの構成と命名規則:https://cloud.google.com/dataform/docs/best-practices-repositories)
レイヤー構造
definitions/
sources/ # external data declarations
intermediate/ # transformations and business logic
output/
reports/ # reporting tables
marts/ # data marts for specific use cases
出力レイヤーにモノリシックなクエリを作らず、再利用とテストができるよう、中間ステップに分割してください。各テーブルでは、1つの変換だけを明確に行うようにします。
インクリメンタル更新と全件更新
config {
type: "incremental",
uniqueKey: ["order_id"],
bigquery: {
partitionBy: "DATE(order_date)",
clusterBy: ["customer_id", "product_id"]
}
}
uniqueKey は配列です(行を追加するのではなく、マージします)。日々増加するテーブル(イベント、トランザクション、ログ)にはインクリメンタル更新を使用し、小規模なディメンションや、ルックバック期間を設定した集計には全件更新を使用してください。
インクリメンタル処理の内部仕様
incremental() コンテキスト関数を使うと、インクリメンタル実行時に新しい行だけを書き込めます。また、${self()} を使うと、構築中のテーブルを参照できます。
config {
type: "incremental",
uniqueKey: ["order_id"],
bigquery: { partitionBy: "DATE(order_date)" }
}
SELECT order_id, order_date, amount
FROM ${ref("orders")}
${when(incremental(), `WHERE order_date > (SELECT MAX(order_date) FROM ${self()})`)}
incremental()は、インクリメンタル実行時にのみ true になります(初回のビルドと--full-refreshでは false です)。${self()}はこのテーブルを参照します。インクリメンタルなWHEREで使うと、ハイウォーターマークを読み取れます。pre_operations { ... }/post_operations { ... }は、ビルドの前後に DDL(権限付与など)を実行します。一度だけ実行する DDL は${when(!incremental(), ...)}で囲み、作成時または全件更新時のみ実行されるようにしてください。追記のたびには実行されません。
Dataform のアサーション
config {
type: "table",
assertions: {
uniqueKey: ["call_id"],
nonNull: ["customer_phone_number", "start_time"],
rowConditions: ["duration >= 0"]
}
}
パイプラインの実行中に、データ品質の問題を自動的に検出します。
ソース宣言:.sqlx ファイルを優先
新しいソース宣言はすべて .sqlx ファイルにしてください(列のドキュメントを記述でき、保守性が高く、依存関係の追跡にも対応しています)。
-- definitions/sources/external_system/table_name.sqlx
config {
type: "declaration",
database: "project_id",
schema: "external_schema",
name: "table_name",
columns: { id: "Unique identifier for records" }
}
既存の .js の declare() ファイルはそのまま残せますが、次回変更する際に .sqlx へ移行してください。
スキーマ設定のルール
definitions/operations/ と definitions/test/ 内のファイルには、schema: 設定を含めてはいけません。Operations はデフォルトの dataform スキーマに、アサーションは workflow_settings.yaml で設定された dataform_assertions スキーマに保存されます。スキーマを明示的に指定すると、競合が発生します。
-- CORRECT
config { type: "operations", tags: ["daily"] }
-- WRONG
config { type: "operations", schema: "dataform", tags: ["daily"] }
ドキュメントの基準(必須)
type: "table" を指定するすべてのテーブルには、必ず包括的な columns: {} ブロックを含めてください(説明文には elements-of-style:writing-clearly-and-concisely を活用してください)。
-- WRONG: no column documentation
config { type: "table", schema: "reporting" }
-- CORRECT
config {
type: "table",
schema: "reporting",
columns: {
customer_id: "Unique customer identifier from source system",
total_revenue: "Sum of all order amounts in USD, excluding refunds"
}
}
説明文の情報源: 上流のソース宣言、サードパーティや API のドキュメント(CRM、ERP、アナリティクス)、計算フィールドのビジネスロジック、およびアナリストに必要な BI ツールのコンテキストです。
ソース宣言にも列の説明を記述してください。下流のテーブルではこれらの説明が引き継がれ、さらに拡張されるため、パイプライン全体でドキュメントの一貫性を保てます。
config {
type: "declaration",
database: "project_id",
schema: "external_api",
name: "events",
description: "Event records from external API",
columns: {
event_id: "Unique event identifier from API",
user_id: "User who triggered the event",
event_type: "Type of event (click, view, purchase, etc.)",
timestamp: "UTC timestamp when the event occurred"
}
}
テスト駆動開発(TDD)ワークフロー
必須の事前知識: superpowers:test-driven-development に従ってください。要件が不明確な新機能では、まず superpowers:brainstorming を使って設計を具体化してから、TDD を開始してください。
Dataform テーブルの開発サイクルは次のとおりです。
- RED — データ品質アサーション/ユニットテストを先に作成し、実行して失敗することを確認します(テーブルはまだ存在しません)。
- GREEN — テストに合格させるための最小限の SQLX を作成します。
- REFACTOR — テストに合格した状態を維持しながら整理します。
先に作成するテストは「どうあるべきか」を定義します。後から作成するテストは「実際にどうなっているか」を確認するだけです。そして「後から」は、たいてい「結局やらない」という意味です。
例
ステップ 1 — まずアサーションを作成(definitions/assertions/assert_customer_metrics.sqlx):
config { type: "assertion", description: "Customer metrics must have valid data" }
SELECT 'Duplicate customer_id' AS test
FROM ${ref("customer_metrics")}
GROUP BY customer_id HAVING COUNT(*) > 1
UNION ALL
SELECT 'Negative lifetime value' AS test
FROM ${ref("customer_metrics")}
WHERE lifetime_value < 0
ステップ 2 — 実行し、失敗することを確認:
dataform run --schema-suffix dev --run-tests --actions assert_customer_metrics
# ERROR: Table customer_metrics does not exist ✓ EXPECTED
ステップ 3 — 最小限の実装(definitions/output/reports/customer_metrics.sqlx):
config {
type: "table",
schema: "reporting",
columns: {
customer_id: "Unique customer identifier",
lifetime_value: "Total revenue from customer in USD"
}
}
SELECT customer_id, SUM(order_total) AS lifetime_value
FROM ${ref("orders")}
GROUP BY customer_id
ステップ 4 — 実行し、合格することを確認:
dataform run --schema-suffix dev --actions customer_metrics
dataform run --schema-suffix dev --run-tests --actions assert_customer_metrics
# No rows returned ✓ TESTS PASS
クイックリファレンス
| タスク | コマンド |
|---|---|
| コンパイルのみ | dataform compile |
| ドライラン | dataform run --schema-suffix dev --dry-run --actions table_name |
| 開発環境でテスト | dataform run --schema-suffix dev --actions table_name |
| 依存関係とともに実行 | dataform run --schema-suffix dev --include-deps --actions table_name |
| タグで実行 | dataform run --schema-suffix dev --tags looker |
| 本番環境へデプロイ | dataform run --actions table_name(開発環境でのテストが成功した後にのみ実行) |
よくある正当化(そして、それが間違っている理由)
| 言い訳 | 実態 | 対処法 |
|---|---|---|
| 「開発環境でテストするには緊急すぎる」 | 本番環境での障害は、開発環境でのテストより多くの時間を浪費します | 3 分のテストで、60 分のデバッグを省けます |
| 「簡単なレポートにすぎない」 | 「簡単な」レポートは、恒久的なテーブルになります | 最初から適切なアーキテクチャを使います |
| 「ビジネス側が待っている」 | 壊れた出力は、関係者の時間を無駄にします | 10 分後の正しい結果のほうが、今すぐの間違った結果より優れています |
「パスをハードコードするほうが ${ref()} より速い」 | 依存関係の追跡と --schema-suffix が機能しなくなります | ソースを宣言し、${ref()} を使います(30 秒) |
| 「リファクタリング/テストの追加/ドキュメントの作成は後でやる」 | 後でとは、決してやらないということです。技術的負債が解消されることはめったにありません | 最初から正しく行います(まずテストを作成し、すぐに columns:{} を追加します) |
| 「後からテストしても同じ結果になる」 | テストを後から行うと動作を確認しますが、テストを先に行うと動作を定義できます | TDD により、設計上の欠陥を早期に発見できます |
| 「内部テーブルの列のドキュメントは任意だ」 | 内部テーブルは、いずれ外部にも公開されます | 常にすべてをドキュメント化します |
| 「遅くまで作業しているので、とにかく動かしたい」 | 疲労はミスの原因になります | 疲れているときこそ、規律がいっそう重要です |
| 「これは[ビジネス上の理由]があるので別だ」 | 例外に対応するためにこそ、ルールがあります | 交渉の余地のないプラクティスに従います |
危険信号 — すぐに停止してください
次のように考えていることに気づいたら、作業を止めてスキルに従ってください。
- 「今回は
--schema-suffix dev/--dry-runを省略しよう」 - 「パスをハードコードするか、
${ref()}の代わりにバッククォートを使おう」 - 「中間レイヤーを作らず、大きなファイルを 1 つ作ろう」
- 「ここではテストは任意だ/後で書こう」
- 「このテーブルには
columns: {}ブロックは不要だ/ドキュメント化は後でやろう」 - 「この新しい宣言には
.jsファイルを使おう」 - 「このオペレーション/テストファイルに
schema:を追加しよう」 - 「技術的負債は後で解消しよう」
時間的プレッシャーへの対処手順
極度のプレッシャーがある状況(2 時間後の役員会議、本番環境の停止など)でも、上記の交渉の余地のない事項は依然として適用されます。開発環境でのテスト、--dry-run、ソースの宣言、columns:{}、テストファースト、基本的な検証にかかるのはいずれも数分で、数時間を節約できます。
省略してよいのは(その後の対応を記録した technical_debt タグ付き TODO を残す場合に限ります)、詳細な独立ドキュメントファイル、ピアレビュー、パフォーマンス最適化です。それ以外は省略できません。
トラブルシューティング
バグ、テストの失敗、予期しない動作が発生した場合は、修正を試みる前に必ず superpowers:systematic-debugging を使ってください。障害が連鎖している場合は、superpowers:root-cause-tracing を使ってください。Dataform 固有のエラーについては、https://cloud.google.com/dataform/docs を参照してください。
- 「テーブルが見つからない」 — ソース宣言が存在することを確認し、
ref()の構文(ソースが宣言済みの場合は引数が 1 つであること)を検証し、スキーマ/データベースが一致していることを確認してから、dataform compileを実行して解決後の SQL を確認します。 - 依存関係の循環 — 直接参照ではなく
${ref()}を使い、循環する連鎖(A → B → A)を探し、Dataform UI で依存関係グラフを確認します。 - タイムアウト — パーティショニング/クラスタリングを追加し、フルリフレッシュから増分処理に切り替え、大規模な変換を小さな中間テーブルに分割します。
近道より規律のほうが速いです。