Feedforce Developer Blog

フィードフォース開発者ブログ

LookML 開発で使っているディレクトリ構造を紹介する

こんにちは、id:masutaka26 です。

この記事は Looker Advent Calendar 2021 の 13 日目の記事です。

qiita.com

昨日は Yappli 阿部さんの「Lookerの目標値やストップワードを、Googleスプレッドシート連携でお手軽管理【Sexy Tech for You #9】」でした。Looker を使うとこのような LookML を書くだけで、ビジネスユーザーが SQL を書くことなく、本業に集中できるのはとても良いですよね。

個人的には、SQL ベースの派生テーブルの中で join するよりも、explore で join したほうが Looker らしく、メンテナンス性が良い気がしました。symmetric 集計が働くため、ファンアウトも避けられます。wikipedia テーブルに関しては、永続的な派生テーブル(PDT)を使って BigQuery のスキャンサイズを抑えるのも良さそうです。

、、、( ゚д゚)ハッ!ついマジレスをしてしまいました。💦

今回は dimension が null の measure を(0 ではなく)ø にする少しマニアックな記事を書く予定でしたが、先日の Looker User Meetup Online #7 で、LookML のディレクトリ構造を知りたいというチャットをお見かけしたので、今回はその話を書くことにしました。

今回のプロジェクトの規模感

プロジェクトの規模感によってディレクトリ構造は変わると思うので、先に書いておきます。

  • Looker インスタンスに 1 つだけ LookML プロジェクトが存在する
  • BigQuery Dataset 76 個
  • .lkml ファイル 277 個
    • .model.lkml ファイル 1 個
    • .explore.lkml ファイル 56 個
    • .view.lkml ファイル 139 個
    • .test.lkml ファイル 78 個
  • LookML 開発者 1 名

デフォルトのディレクトリ構造?

この記事を書くまで誤解をしていたのですが、デフォルトのディレクトリ構造というものはなかったのですね。この記事を書くために改めて Blank Project を作ったら、ファイルもディレクトリも何もないプロジェクトが作られました。

モデルファイルを作るとこのようなコードが展開されるので、.view.lkml に関しては /views/ 以下に作る方が多いと思います。私もそうでした。

include: "/views/*.view.lkml"                # include all views in the views/ folder in this project
# include: "/**/*.view.lkml"                 # include all views in this project
# include: "my_dashboard.dashboard.lookml"   # include a lookml dashboard called my_dashboard

例えばこのようになります。

feedmatic.model.lkml
views
├── all_media.view.lkml
└── ga.view.lkml

開発初期は feedmatic.model.lkml に view 以外の、explore や datagroup などをズラズラと書いていました。

特別なファイル形式を知る

ご存知の通り、LookML のファイル形式は .lkml です。

LookML 開発が続くと .model.lkml.view.lkml などが増えていきますが、この中で唯一意味を持つのが .model.lkml です1。その他のファイル形式は整理のために自由に作ることが出来ます。

例えば feedmatic.model.lkml を作ると、feedmatic というモデルが定義されます。https://{{your looker domain}}/projects で確認できます。all_media.view.lkml を作っても、何かが作られるわけではありません。

以上の知識を持った上で、公式ドキュメントを読むと理解が深まるかもしれません。

最近使っているディレクトリ構造

こんな感じです。それぞれ解説していきます。

bigquery
├── spreadsheet1
│   ├── define.json
│   └── schema.json
└── spreadsheet2
    ├── define.json
    └── schema.json
model1.model.lkml
explores
├── corp
│   ├── base.explore.lkml
│   ├── name1.explore.lkml
│   └── name2.explore.lkml
├── explore1.explore.lkml
└── explore2.explore.lkml
views
├── corp
│   ├── base.view.lkml
│   ├── name1.view.lkml
│   └── name2.view.lkml
├── view1.view.lkml
└── view2.view.lkml
tests
└── model1
    ├── corp
    │   ├── name1.test.lkml
    │   └── name2.test.lkml
    ├── explore1.test.lkml
    └── explore2.test.lkml
manifest.lkml

bigquery/

いきなり LookML 関係ありません。💦

BigQuery はデータソースに Google スプレッドシートを指定でき、そのスキーマ定義はコード化することが出来ます。

コード化することで変更履歴を Git で管理できますし、BigQuery CLI を使って簡単に Dataset や Table を作ったり、削除したりが出来ます。

$ bq mk spreadsheet1
$ bq mk --external_table_definition=./define.json spreadsheet1.gsheet
$ bq rm -r spreadsheet1

破壊的な変更をする時は、バージョン名を付けた Dataset を新規作成し、LookML から参照先を変えます。こうすることで、本番環境に影響を与えずに開発することが出来ます。

$ bq mk spreadsheet1_v2
$ bq mk --external_table_definition=./define.json spreadsheet1_v2.gsheet

スキーマ定義は公式ドキュメントをご覧下さい。需要があればそんな記事を書きます。

他の Dataset は ETL ツール2が作るためコード化はしていません。

model1.model.lkml

中心となるこのファイルは軽いです。本当にこの程度しか書いていません。

connection: "docs_bigquery_db"

include: "/explores/**/*.explore"
include: "/tests/model1/**/*.test"

named_value_format: jpy_0 {
  value_format: "\¥#,##0"
}

named_value_format: jpy_1 {
  value_format: "\¥#,##0.0"
}

# for test
access_grant: can_view_explores_for_tests {
  user_attribute: view_explores_for_tests
  allowed_values: ["yes"]
}

必要な定義は connectioninclude だけです。

include 対象を全ての .explore.lkml と、このモデルに関連するテスト(tests/feedmatic/ 以下全ての .test.lkml)だけにしていることがポイントです。つまり .model.lkml.explore.lkml と自分の .test.lkml しか知りません。

あとは蛇足で、named_value_format と、前回紹介したテストに必要な access_grant だけです。

explores/

1 つの explore を 1 つのファイルに定義しています。

explores/explore1.explore.lkml はこのように書いています。.explore.lkml.view.lkml しか知りません。

include: "/views/**/*.view"

explore: explore1 {
  # ...
}

紆余曲折あり、explores/corp/name1.explore.lkml のような取引先ごとの explore もあります。

基本となる explores/corp/base.explore.lkml はこのような定義です。ファイル名と explore 名を変えていることがポイントです。Ruby の慣習を参考にしました。

explore: corp_base {
  extension: required
  # ...
}

corp_base explore を継承する、各取引先の explore はこのような定義です。

include: "./base.explore"
include: "/views/**/*.view"

explore: corp_name1 {
  extends: [corp_base]
  # ...
}

views/

view も explore と同様に、1 view 1 ファイルに定義しています。

views/view1.view.lkml です。.view.lkml.model.lkml, .explore.lkml, .test.lkml の誰も知りません。

view: view1 {
  # ...
}

取引先ごとの view も同じです。views/corp/base.view.lkml はこんな感じで、

view: corp_base {
  extension: required
  # ...
}

継承先の views/corp/name1.view.lkml はこんな感じです。

include: "./base.view"

view: corp_name1 {
  extends: [corp_base]
  # ...
}

tests/

テストはかなり書いており、2021-12-13 現在、184 もあります。

ほぼ explore 単位でファイル分割しています。分割することで、ファイル単位のテストが可能になっています。

こちらのベストプラクティスに従っています。

テスト対象はこんな感じです。

  • (1) LookML で特別な実装をしていて、壊れても気づくのが難しそうな実装
  • (2) primary_key が重複していないか? null になっていないか?を全ての view に対して

(2) は前回詳しく書きました。

実行に時間がかかることが悩みで以前こんな Topic を作りましたが、反応ゼロでした。みなさん課題ではないのかしら?💦

primary_key は壊れた時に気づくのが難しく、LookML 開発者が私だけということもあるため、機械的に全ての primary_key をテスト出来るようにしています。ただ、全テストは結構時間がかかるので、日に 1 回くらいの頻度で手動実行しています。CI したい...。

manifest.lkml

ディレクトリ構造とは関係ありませんが、manifest.lkml についても触れておきましょう。

bigquery/ の項で書いたとおり、テーブル定義に破壊的な変更を加える時は dataset_v2 のように Dataset 名にゆるふわバージョンを付けています。つまり割とカジュアルに Dataset 名が変わります。

そのため、このように manifest.lkml で全ての Dataset 名を定義しています3。Dataset 名は複数箇所で使われ得るためです。

constant: table_name1  { value: "`table1_v2.gsheet`" }
constant: table_name2  { value: "`table2.view`" }

利用例です。

sql_table_name: @{table_name1} ;;

まとめ

現在 LookML 開発で使っているディレクトリ構造を紹介しました。中規模くらいまでの LookML プロジェクトには使える実感があります。

ただ、最近はファイル数が多くなってきて、.view.lkml を追加した時に変更する .explore.lkml.test.lkml の距離が遠く、実装しづらい課題があります。

Refinements を使えば解決できるのだろうか、もっと再利用性のあるコードにしたいなど、悩みは尽きないです。

こちらの記事は読んで手も動かしたのですが、巨大なファイルを分割する、Blocks のようなライブラリをカスタマイズする(?)、以外の使い方を見いだせていません。

皆さんのディレクトリ構造も是非教えて下さい!

2021-12-29 追記

What is the looker recommended folder structure for LookML development ? | Looker Community

Looker Community にもお悩みの方がいらっしゃいました。返信している Dawid さんはよくお見かけする方で、かなりの熟練者だと思います。

そんな彼も、今回私が書いた記事のような物理的な構造が良いのか、論理的な構造が良いのか、未だに試行錯誤しているようです。

始めは物理的なディレクトリ構造から始めて、徐々に変化しながら論理的な構造に近づくのかもしれません。その頃にはステージに応じたベストプラクティスが出ていると良いですね。


  1. 正確に書くと manifest.lkml.strings.json などもありますが、一旦考えなくて良いと思います。

  2. Funnel.io を使っています。

  3. 1 行で書いているのはソートしやすくするためです。