kintoneをデータソースとしたDWHへのデータ連携で注意すべきポイントとサイボウズでの対策の紹介
はじめに
弊社ではドッグフーディングをしていることから、kintoneを業務で利用しています。
そのため、各チームが自由にアプリを作成し、そこに有用なデータも多く貯められています。
複数のアプリや、別システムにあるデータとあわせて集計・分析したい要望があることから、kintoneアプリのデータをデータウェアハウス(以下、DWH)へと連携することも多くあります。
数多くのアプリを連携してきた経験から、連携の失敗などで課題を感じることが多かったです。
その課題を解消し、さらにその仕組みを活用して連携の工数を削減することができたので、どのような工夫を行っているか紹介したいと思います。
本記事はkintoneからのデータ取得方法については記載しません。
kintoneをデータソースとして使う際の懸念点
前提として、弊社ではkintoneアプリは各チームによってある程度自由に作成し、管理・運用されています。
次の節にはよく発生する問題について取り上げて解説していますが、この他にも細かい問題は多く発生しています。
フィールドコードの変更による連携失敗
大きな問題としてはこちらが挙げられます。
kintoneのデータ取得に関しては、「ファイルに書き出す」からCSVに書き出してインポートする方法と、API(またはそれを利用するETL/ELTツール)から連携する方法の2つに大別されます。
データチームでは後者で、現時点ではEmbulkを用いたデータ連携を行っております。
Embulkでは、kintoneアプリの情報と取得するフィールドの情報、出力先のBigQueryの情報とスキーマを記載したファイルを準備することでkintoneアプリからBigQueryへの連携ができる、という仕組みになっています。
kintoneでは存在しないフィールドコードを指定してAPIでのデータ取得を行った際、存在しないフィールドコードに関してはレスポンスがありません。
具体的には、例えばtest2というフィールドコードのフィールドが、test2_newなどにリネームされてしまった場合、Embulkであれば、以下の図のような振る舞いとなってしまい、エラーも出しません。(APIで各自で実装した場合はその実装に依りますが、どちらにしろ存在しないフィールドコードのレスポンスはありません。)
フィールドの用途の変更(フィールド名の変更)
フィールドコードはそのままに、フィールド名を変更し、いつの間にかフィールドの役割が変わっているケースです。
こちらは特に全てがnullになるなどデータの変化も少なく、検知が難しいです。
例えば、「アドレス」というフィールドコードで、「メールアドレス」というフィールド名だったものが、いつのまにか「アドレス」というフィールドコードのまま「住所」などになっているケースです。
懸念点をケアする仕組み
前節の問題を放置することでデータの品質が悪化し、最悪のケースとして間違った意思決定を招く可能性があります。
このような課題感を解決しつつ、よくあるkintone連携依頼をいかに効率よく対応するか、という点について、データチームで行っている仕組みを紹介します。
現在のシステム構成
過去の記事にあるように、kintoneアプリからBigQueryに取り込む際は、全てのカラムを文字列型として定義したrawテーブルを作成します。
それから、IntegerやDateなど、各カラムの型に合わせてdbtで変換したsourceテーブルを作成します。
利用者にはsourceテーブルのみを公開しています。
ちなみに、kintone1アプリがBigQueryでの1テーブルとなります。
連携するアプリの管理アプリの準備
こちらが懸念点をケアする仕組みの肝となる部分になります。
以下のような「DWHへ連携するkintoneアプリを管理する」アプリを準備します。
各フィールドの説明は以下の表の通りです。
フィールド名 | 用途 |
---|---|
アプリID | 連携するアプリのID |
ドメイン名 | 連携するアプリがあるサブドメイン (弊社では対象サブドメインが複数あるため。) |
テーブル名 | DWHでのテーブル名 |
ステータス | 連携状況(連携中/準備中) |
絞り込み条件 | 取得するレコードを絞るためのクエリ |
フィールド名 | 連携するフィールドのフィールド名 |
フィールドコード | 連携するフィールドのフィールドコード |
BQカラム名 | DWHでのカラム名 |
BQカラムタイプ | DWHでのカラムの型 |
BQカラムモード | DWHでのカラムのモード |
BQ概要 | DWHでの概要(description) |
BOOL_TRUE項目 | BOOLEANを利用する場合、Trueとなる項目 |
マスキング | マスキング対象かどうか |
サブテーブルに関しては、後述の各スクリプトではBQカラム名に.
を含むものをサブテーブルに属するフィールドとして判断するようなロジックになっています。
(フィールド名・フィールドコードは他チームで自由に作成・運用する関係で命名規則を浸透させることが難しいため)
kintone連携のシステムの仕様について
データチームでは、この管理アプリを利用して「連携チェック・連携用スクリプト生成」の2つのスクリプトを動かしています。
それぞれ次の項で説明しますが、前提として管理アプリをDWHに連携し、テーブルとして利用できる状況にしておきます。
(もしこれを読んでチャレンジされる方は都度APIで取得する仕組みにしても問題ありません。)
連携チェック
1つ目のスクリプトは、連携対象アプリの変更を検知する「連携チェック」を行うスクリプトです。
連携管理アプリに連携を作成した当時の情報が残っているので、それとの差分を確認することで連携当時と比べて変化があったか、をチェックしています。
以下のフローチャートのように、管理アプリの各レコードが正常か定期的に確認を行っており、意図しない変更がなされた場合に、Slackで通知されるようになっています。
連携用スクリプト生成
2つ目のスクリプトは、連携用ファイルを自動生成するスクリプトです。
データチームではkintoneのDWHへの連携には「Embulk用のliquidファイル・dbt用のyaml・sqlファイル」があれば連携できるようになっており、そのファイル構成もイレギュラーがない同等の構成となっています。
そのため、連携アプリ管理アプリの内容から上記の連携用ファイルをスクリプトにより自動で生成することができます。
この自動生成の仕組みにより、連携の依頼の度に一つ一つ手書きで作成するのに比べて大きな工数削減ができています。
参考までに、生成されたファイルをお見せします。
Embulk用liquidファイルとその説明
{% include 'in' %}
domain: ******.cybozu.com
app_id: 1
query: test1 like "value"
fields:
- name: test1
type: string
- name: test2
type: string
- name: test3
type: string
- name: test4
type: string
- name: subtable
type: string
filters:
- type: rename
columns:
test1: test_1
test2: test_2
test3: test_3
test4: test_4
subtable: sub_table
{% include 'out' %}
table: raw_test_table
共通で利用されるkintone/BigQueryの情報は_in.yaml.liquidや_out.yaml.liquidという形で別ファイルでまとめて管理することで省力化しています。
連携管理アプリで「絞り込み条件」とあったものは、ここでqueryとしてEmbulkに認識をさせています。
また、サブテーブルに関してはこのように記載することで、レスポンスのjsonをstringのカラムとしてテーブル化することができます。
rawテーブルはひとまずこのままテーブル化し、sourceテーブルにてstruct型として展開します。
dbt用SQLとその説明
select
if(test_1 = '', null, test_1) as test_1
, cast(if(test_2 = '', null, test_2) as integer) as test_2
, test_3 = '済' as test_3
, split(if(test_4 = '', null, test_4), '\n') as test_4
, array(
select as struct
if(json_extract_scalar(json_data, '$.value.name.value') = '', null, json_extract_scalar(json_data, '$.value.name.value')) as name
, if(json_extract_scalar(json_data, '$.value.address.value') = '', null, json_extract_scalar(json_data, '$.value.address.value')) as address
from unnest(json_extract_array(sub_table)) as json_data
) as sub_table
from {{ source('test_dataset', 'raw_test_table') }}
dbtでは、上記のファイルを利用してsourceテーブルへと変換しています。kintoneからrawテーブルを介さずに直接作成も可能なのですが、dbtでリネージを表示させる関係でrawテーブルからdbtを用いて変換させています。
kintoneからのデータ取得では、取得方法によってはnull
でなく""
として空文字が入るケースがあるため、基本的には一度null
への置換の上、必要な型への変換を行っています。
ちなみにtest_3は管理アプリでBOOLEANとして連携するよう設定しているので、BOOL_Trueの値である済
であればTrue
になるようにクエリが生成されます。
また、ユーザー選択やドロップダウンの複数選択など、kintoneでは複数の値が入るケースがあります。
今回は一例としてtest_4をそのフィールドとして取り扱っています。
仕様として\n
で区切った値が返ってくるので、\n
でsplitすることで文字列のarray型としてDWHのテーブルでは持つようになっています。
サブテーブルに関しては、rawテーブルでstring型のカラムとして保存されているところからjsonとして各カラムの値を読み出し、struct型として格納しています。
dbt用Yamlとその説明
version: 2
sources:
- name: test_dataset
tables:
- name: raw_test_table
columns:
- name: test_1
- name: test_2
- name: test_3
- name: test_4
- name: sub_table
models:
- name: test_table
config:
contract:
enforced: true
columns:
- name: test_1
description: テスト_1
data_type: STRING
constraints:
- type: not_null
policy_tags:
- "{{ env_var('PII_TAG_PATH') }}"
- name: test_2
description: テスト_2
data_type: INTEGER
- name: test_3
description: テスト_3
data_type: BOOLEAN
constraints:
- type: not_null
- name: test_4
description: テスト_4
data_type: ARRAY<STRING>
- name: sub_table
description: サブテーブル
data_type: ARRAY
- name: sub_table.name
description: なまえ
data_type: STRING
- name: sub_table.address
description: じゅうしょ
data_type: STRING
モードでREQUIREDであれば以下のようにnot_null制約を追加するようにしています。
constraints:
- type: not_null
Google CloudでPolicy Tagを利用してのマスキングを行っているため、マスキングにチェックを入れたものは、別途環境変数で定義したPolicy Tagのパスを当てるようにしています。
まとめと今後の展望
データチームで利用しているkintone連携のチェックおよびスクリプト作成の仕組みを紹介しました。
これらの仕組みにより、連携しているアプリに修正が入った場合に気づけるようになり、DWHへの連携依頼のリードタイムも大きく短縮することができました。
2025年9月末時点では、kintoneアプリのDWHでのテーブル化の依頼があれば、データチームのメンバーが連携アプリ管理アプリにてレコードを作成します。
そして、同じくメンバーにより「スクリプトを生成するスクリプト」を実行の上、作成されたスクリプトをGitで管理しています。
今後の展望として、以下のような活用にチャレンジしたいと思っています。
- 連携の度に都度生成して連携(Git管理しない)
- データチーム以外でもレコード作成し、データチームの承認の上自由にテーブルの作成が可能
Discussion