jOOQ と Flyway で立ち向かう、自社サービスの保守運用 #jjug_ccc #ccc_ab1

Preview:

Citation preview

CyberAgent, Inc. All Rights Reserved

JJUG CCC Fall 2015#jjug_ccc #ccc_ab1

jOOQ と Flyway で立ち向かう、自社サービスの保守運用(仮)

Yusuke Ikeda / @yukungCyberAgent, Inc.

● 池田 裕介( @yukung )

● 株式会社サイバーエージェント 技術本部

● サーバサイドエンジニア

● Ameba プラットフォームの API 設計・運用

Java や Groovy のコミュニティによく出没します

About me

https://www.cyberagent.co.jp/techinfo_detail/id=11016&season=2015&category=sever

Spring in Summer で発表しました

CyberAgent, Inc. All Rights Reserved

突然ですが、質問です

今、何かのサービスやシステムの

保守をしていますか?

ソースコードのバージョン管理はしていますか?

ユニットテストは書いていますか?

CI は回っていますか?

データベースのスキーマも

バージョン管理していますか?

CyberAgent, Inc. All Rights Reserved

本日のゴール

本日のゴール

アプリだけでなく DB もセットで

バージョン管理+ CI して、

「つらくない」😁快適な運用保守ライフを送ろう!

そう、

ならね。

データベーススキーマのバージョン管理

データベーススキーマとアプリケーションコードとの乖離

保守フェーズで抱える悩み

想定しているシチュエーション

運用・保守フェーズ

新規開発フェーズや、市場調査のための試行錯誤・技術検証フェーズでは

参考にならないかもしれません。

既存 の DB スキーマが存在

DB スキーマを開発者が設計に関わることができ、 DB スキーマの正規化が

有効になされているプロジェクトでは、必ずしも効果的ではないかもしれません。

DB スキーマとコードを効果的に管理できていない

フレームワークが提供している DB マイグレーションの仕組みなどを用いて、

既に DB マイグレーションを開発プロセスとして取り入れている人には、当たり前の話です。

Contents

事例紹介

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

Ameba プラットフォームについて

Contents

事例紹介

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

Ameba プラットフォームについて

コミュニティ + ゲーム + メディ

アのプラットフォーム

2012 年 4 月ローンチ

現時点で延べ 300 サービス

会員数:約 3,900 万

月間 PV :約 144 億

月間投稿数:約 3,000 万

Ameba プラットフォーム

池田裕介
TODO Ameba プラットフォームの話は削る

Ameba のサービス提供フロー

ディベロッパー

App App App

2. 開発Ameba Developer Center 1. 登録

3. 申請

プラットフォームで提供するサービスの情報を集約・管理し、審査や公開を行う 5. 公開

認証システム

課金システム

分析クラスタ

ソーシャルグラフ API

Ameba プラットフォーム

4. データ連携

Contents

事例紹介

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

Ameba プラットフォームについて

Architecture

社内ログ解析基盤

Patriot

Database

developer.amebame.com

Mail Server

admin.developer.amebame.com

Backend service (Batch, etc)

Reverse Proxy

SPA + REST API

CyberAgent, Inc. All Rights Reserved

いたって普通のサーバサイドアーキテクチャで

Contents

事例紹介

Ameba プラットフォームについて

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

CyberAgent, Inc. All Rights Reserved

事案 1

既存の仕様を…調べてたら

よーしパパ MySQL 調べちゃうぞ〜

mysql> DESC hoge_table; っと…

あれっ!?このカラム、

ステージングにはあるけど

本番環境には無いぞ??

しょうがない、

定義書の変更履歴見てみるか…

事案 1

必要なのかどうか

分からない

ある項目が

いつ何のために

追加されたのか

わからない

影響が怖いので

触りたくない

→ → デッドコードが増える リファクタリングしにくい 保守性↓↓🙅

CyberAgent, Inc. All Rights Reserved

事案 2

あれっ… A さん、なんか定義書に無い

テーブルが存在してるんですが…

あぁ…それ定義書に反映してないんだ

と思うよー、最新にしといてー

…めんどくさ…クッDDL から

リバースエンジニアリングするか…

→ 時間とともに秘伝のタレとなっていく 定義書 is 何

事案 2

CyberAgent, Inc. All Rights Reserved

事案 3

この SQL 、動くけど使ってない…?

コミットログ見てみるか…

池田裕介
TODO 事案3 は蛇足なので削る

CyberAgent, Inc. All Rights Reserved

事案 4

とある DAO クラスを

眺めていて

これはつらい…

何が『つらい』?

もう一度

事案 4

● 変更による影響が拾えない

● typo していても動かしてみないと気づかない

● JPA なら Criteria API 、 MyBatis なら Generator の

Criteria を使えばタイプセーフにはできるけど、抽象

度が上がってコードの可読性が下がる

つらいところ

文字列で記述されている

DB スキーマがコードに埋め込まれている

実際に動かさないとわからない

MyBatis の Criteria の例

池田裕介
TODO 配布時には削る

事案 4

● DB スキーマ変更の影響がエラーで検知できない

○ コード中に DB スキーマ情報が埋め込まれている

○ クエリが XML やプロパティファイルに記述して

あっても同じ

● 自動化されたユニットテストと CI 必須

つらいところ

文字列で記述されている

DB スキーマがコードに埋め込まれている

実際に動かさないとわからない

事案 4

● SQL の文法エラーですら拾えない

○ 自動化されたユニットテストと CI 必須

○ 保守コストは高い

● DB スキーマの情報と、アプリケーションコードが地

続きになっていない

○ DB スキーマの変更も動かす前に検知したい

つらいところ

文字列で記述されている

DB スキーマがコードに埋め込まれている

実際に動かさないとわからない

DB スキーマがコードに埋め込まれている

事案 4文字列で記述されている

実際に動かさないとわからない

つらくない開発を!もっと!

変更を「検知しやすく」、

可読性が高く「理解しやすい」状態

を保つ仕組みが欲しい

Contents

事例紹介

Ameba プラットフォームについて

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

DB スキーマの変更を追跡したい

アプリケーションコードだけでなく、 DB スキーマもバージョン管理する

スキーマの変更管理を楽にしたい

DB スキーマの変更管理を人手で行うのではなく、自動化して常に一貫性を保つ

変更検知を静的に、かつ

スキーマとコード の乖離を防ぐ

DB スキーマの変更によるアプリケーションコードへの影響を、動作させる前に検知

これらのワークフローを一気通貫に行いたい

解決したいこと

可読性を落とさずに、バージョン管理されたDB スキーマ情報をアプリケーションコードに活かす

DB スキーマの変更を追跡したい

スキーマの変更管理を楽にしたい

変更検知を静的に、かつ

スキーマとコード の乖離を防ぐ

解決したいこと

データベーススキーマのバージョン管理

データベーススキーマとアプリケーションコードとの乖離

DB スキーマの変更を追跡したい

スキーマの変更管理を楽にしたい

変更検知を静的に、かつ

スキーマとコード の乖離を防ぐ

解決したいこと

Flyway

jOOQ

Contents

事例紹介

Ameba プラットフォームについて

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

CyberAgent, Inc. All Rights Reserved

Flyway

Flyway とは

● DB マイグレーションツール

○ DB スキーマの構成管理を手軽に自動化できる

○ DDL や DML をマイグレーションスクリプトとして記述する

● Ant や Maven, Gradle などのビルドツールと一緒に利用するとよい

○ アプリケーションのビルドに DB スキーマの構成管理を統合でき

● CLI や Java API からでも使える

○ サーバサイドだけでなく Android の SQLite でも使える

● Git などのバージョン管理システムの管理下に置くことで、 DB スキー

マもコードで表現できる( Infrastructure as Code に似た考え方)

CyberAgent, Inc. All Rights Reserved

Flyway のセットアップ

Gradle (v2.1 以降 )

plugins { id "org.flywaydb.flyway" version "3.2.1"}flyway { url = "jdbc:mysql://host:port/sampledb" user = "sample_user" password = "any_password"}

Flyway のセットアップ

create table PERSON ( ID int not null, NAME varchar(100) not null);

DDL をそのまま記述できる。

マイグレーションスクリプト

デフォルトでは以下の命名規則にしたがって記述する。設定で変更することもできる。

スクリプトファイルの命名規則

V2__Add_new_table.sqlプレフィックス

バージョン

ドット (.) もしくはアンダースコア (_)で句切られた数字

セパレータ

アンダースコア 2 つ(__)

説明

バージョン管理用テーブルに記録される

サフィックス

デフォルトでは以下のようなファイル配置とする。設定で変更することもできる。

スクリプトファイルの配置

プロジェクトルート

src

main

java

db

migration

resources

V0.2__Add_new_table.sql

CyberAgent, Inc. All Rights Reserved

Flyway でマイグレーション

baseline コマンドを使う( init は flyway 4.0 で削除予定)$ ./gradlew flywayBaseline -i:flywayBaseline (Thread[Daemon worker Thread 2,5,main]) started.:flywayBaselineExecuting task ':flywayBaseline' (up-to-date check took 0.0 secs) due to: Task has not declared any outputs.Flyway.init() is deprecated. Use baseline() instead. Will be removed in Flyway 4.0.Database: jdbc:h2:file:./build/test (H2 1.4)Creating Metadata table: "PUBLIC"."schema_version"Schema baselined with version: 1:flywayBaseline (Thread[Daemon worker Thread 2,5,main]) completed. Took 0.012 secs.

スキーマ管理テーブルの作成

スキーマ管理テーブルの作成

マイグレーションの適用状態を管理する“ schema_version” というテーブルが作成される。

info コマンドを使う。

$ ./gradlew flywayInfo -i:flywayInfo (Thread[Daemon worker,5,main]) started.:flywayInfoExecuting task ':flywayInfo' (up-to-date check took 0.0 secs) due to: Task has not declared any outputs.Database: jdbc:h2:file:./build/test (H2 1.4)+---------+-----------------------+---------------------+---------+| Version | Description | Installed on | State |+---------+-----------------------+---------------------+---------+| 1 | << Flyway Baseline >> | 2015-11-25 14:39:19 | Baselin || 2 | Add new table | | Pending |+---------+-----------------------+---------------------+---------+

:flywayInfo (Thread[Daemon worker,5,main]) completed. Took 0.014 secs.

マイグレーションの状態を確認

State “が Pending”なのでまだ適用されていない

migrate コマンドを使う。

$ ./gradlew flywayMigrate -i:flywayMigrate (Thread[Daemon worker Thread 2,5,main]) started.:flywayMigrateExecuting task ':flywayMigrate' (up-to-date check took 0.0 secs) due to: Task has not declared any outputs.Database: jdbc:h2:file:./build/test (H2 1.4)Validated 2 migrations (execution time 00:00.003s)Current version of schema "PUBLIC": 1Migrating schema "PUBLIC" to version 2 - Add new tableSuccessfully applied 1 migration to schema "PUBLIC" (execution time 00:00.017s).:flywayMigrate (Thread[Daemon worker Thread 2,5,main]) completed. Took 0.045 secs.

マイグレーションする

version 2 のスクリプトが適用された

マイグレーションスクリプトが適用され、バージョンが 1 つ進んだ

$ ./gradlew flywayInfo:flywayInfo+---------+-----------------------+---------------------+---------+| Version | Description | Installed on | State |+---------+-----------------------+---------------------+---------+| 1 | << Flyway Baseline >> | 2015-11-25 14:39:19 | Baselin || 2 | Add new table | 2015-11-25 14:46:56 | Success |+---------+-----------------------+---------------------+---------+

再度、状態を確認

State “が Success”になった

再度、状態を確認

PERSON テーブルが作成されている。

DB の現在の状態と、マイグレーションスクリプトの内容に一貫性があるかを検証する

create table if not exists PERSON ( ID integer primary key auto_increment, NAME varchar(100) not null);alter table PERSON add column AGE integer not null;

マイグレーションの状態を検証する

DB の状態と齟齬がある内容に変更

validate コマンドを使い、マイグレーションスクリプトの内容を検証する$ ./gradlew flywayValidate:flywayValidate FAILED

FAILURE: Build failed with an exception.

* What went wrong:Execution failed for task ':flywayValidate'.> Error occurred while executing flywayValidate Validate failed. Migration Checksum mismatch for migration 2 -> Applied to database : 1401482110 -> Resolved locally : 1349563094

マイグレーションの状態を検証する

スクリプトがチェックサムエラーになる

repair コマンドを使い、マイグレーション情報を削除してチェックサムを最新に更新$ ./gradlew flywayRepair -i:flywayRepair (Thread[Daemon worker Thread 3,5,main]) started.:flywayRepairExecuting task ':flywayRepair' (up-to-date check took 0.0 secs) due to: Task has not declared any outputs.Database: jdbc:h2:file:./build/test (H2 1.4)Repair of failed migration in metadata table "PUBLIC"."schema_version" not necessary. No failed migration detected.Updating checksum of 2 to 1401482110 ...Metadata table "PUBLIC"."schema_version" successfully repaired (execution time 00:00.005s).Manual cleanup of the remaining effects the failed migration may still be required.:flywayRepair (Thread[Daemon worker Thread 3,5,main]) completed. Took 0.014 secs.

マイグレーションを修復する

チェックサムが更新され修復される

clean コマンドを使うと、スキーマが全て削除される(やり直しはできないので、バックアップ必須)

$ ./gradlew flywayClean -i:flywayClean (Thread[Daemon worker Thread 4,5,main]) started.:flywayCleanExecuting task ':flywayClean' (up-to-date check took 0.0 secs) due to: Task has not declared any outputs.Database: jdbc:h2:file:./build/test (H2 1.4)Cleaned schema "PUBLIC" (execution time 00:00.008s):flywayClean (Thread[Daemon worker Thread 4,5,main]) completed. Took 0.023 secs.

スキーマを削除する

CyberAgent, Inc. All Rights Reserved

Flyway の留意点

巻き戻しはサポート外

● 既存データの取扱いや、参照整合性制約など考慮すべきことが多い

● 運用しているデータベースについては、スナップショットからリスト

アする方が安全

複数環境や、並行開発への対応

● 特定のバージョンまで適用したい

○ target オプションを使ってバージョンを指定する

○ 運用環境でデプロイ時に指定するのは事故のもと

● ブランチを切って並行に開発している場合のバージョンの衝突

○ Flyway は適用されているバージョンより古いものは適用されない

○ outOfOrder オプションを true にすると、古いバージョンも適

用される

○ ファイル名を数字ではなくタイムスタンプにすると良い

■ V20151125114035__Add_age_column.sql

Contents

事例紹介

Ameba プラットフォームについて

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

CyberAgent, Inc. All Rights Reserved

jOOQ

jOOQ とは

● SQL を Java コードで DSL で表現して DB アクセスできるライブラ

● JPA のように DB へのアクセスを抽象化するのではなく、できるだけ

SQL をそのままアプリケーションコードとして表現することにこだわ

● 裏側で何をやってるかを隠すのではなく、コード上から何をやってい

るかがわかるように

● “読み方は joke” らしい(海外の人の発音を聞くと「ジューク」の方が

近い?)○ https://groups.google.com/forum/#!msg/jooq-user/SGG7J5ulVBs/1l3XTtSVu9AJ

百聞は一見にしかず

著者の誕生日が 1920 年以降で、名前が Paulo さんの 書籍一覧を取得する SQL (本のタイトル昇順)

SELECT * FROM author a JOIN book b ON a.id = b.author_idWHERE a.year_of_birth > 1920 AND a.first_name = 'Paulo'ORDER BY b.title

サンプル

jOOQ …で同じクエリを記述すると

Result<Record> result =create.select() .from(AUTHOR.as("a")) .join(BOOK.as("b")).on(a.ID.equal(b.AUTHOR_ID)) .where(a.YEAR_OF_BIRTH.greaterThan(1920) .and(a.FIRST_NAME.equal("Paulo"))) .orderBy(b.TITLE) .fetch();

サンプル

CyberAgent, Inc. All Rights Reserved

jOOQ の特徴

jOOQ の特徴

● Database First

○ 既存の DB スキーマを重視

■ レガシーなシステムの DB スキーマにも対応

○ 世の中、必ずしもキレイに正規化されたスキーマ

だけじゃないですよね?

○ 既存のふざけた DB スキーマに悪態つきつつも、

現実と戦うことは多いはず

この DB スキーマふざけてるな!!

誰だ設計したのは!!💢💢

レガシーDB

jOOQ の特徴

● Database First

○ 逆に言うと、以下の様な状況なら JPA の方が生産性は高いと思い

ます

■ 自分たちで DB スキーマをコントロールできる

■ きちんと正規化されているスキーマを相手にできる

○ クエリの自動生成機能などはなく、自分でガリガリクエリを書く必

要がある

■ 生産性の高さは謳っていない

■ 一応、 DAO クラスを生成ツールで自動生成できる

● 単純な CRUD ならそれを使うと実装しなくてもよい

● Typesafe SQL

Result<Record> result =create.select() .from(AUTHOR.as("a")) .join(BOOK.as("b")).on(a.ID.equal(b.AUTHOR_ID)) .where(a.YEAR_OF_BIRTH.greaterThan(1920) .and(a.FIRST_NAME.equal("Paulo"))) .orderBy(b.TITLE) .fetch();

jOOQ の特徴 ● SQL キーワードをメソッドチェーンで繋ぐ

● テーブル名やカラム名、演算子も Java オブ

ジェクトやメソッドで表現

● 文字列をできるだけ使わない

● typo やスキーマの変更がコンパイルエラーとし

て検知できる

● IDE の補完がガシガシ利く

jOOQ の特徴

● Code Generation

○ DB スキーマのメタデータから Java オブジェク

トを生成(生成ツールが付属)

○ DB スキーマの変更は再生成することで反映

○ 一貫性がなければコンパイルエラーとして検知

○ DB スキーマとアプリケーションコードが地続き

jOOQ の特徴

● Active Records

○ 行は自動生成されたクラスのインスタンスにマッ

ピング

○ 自前の POJO にもそのままマッピングできる

jOOQ の特徴

● 他にも

○ Multi-Tenancy

■ 複数スキーマや共有スキーマに対応

○ Standardisation

■ RDBMS ごとの方言の差異を DSL で吸収

● …などなど

CyberAgent, Inc. All Rights Reserved

jOOQ のいいところ

jOOQ のいいところ

● ドキュメントやチュートリアル、サンプルコードが

充実している

○ 公式ドキュメントがすごい(英語)

○ ビデオチュートリアルや各種フレームワーク/ラ

イブラリとの連携サンプルコードもたくさん

● Users Group や Stack Overflow も活発に回答が

● 商用ライセンスもある

jOOQ のいいところ

● 依存ライブラリが少ない

○ 標準では依存ライブラリなし

○ 設定により特定の機能を有効にすることで幾つか

のライブラリに依存する

$ ./gradlew dependencies:dependencies------------------------------------------------------------Root project------------------------------------------------------------compile - Compile classpath for source set 'main'.\--- org.jooq:jooq:3.7.1

runtime - Runtime classpath for source set 'main'.\--- org.jooq:jooq:3.7.1

testCompile - Compile classpath for source set 'test'.\--- org.jooq:jooq:3.7.1

testRuntime - Runtime classpath for source set 'test'.\--- org.jooq:jooq:3.7.1

jOOQ のいいところ

● 後方互換性も大事にされており、活発に開発されてい

○ v3.7 で Java8 に正式対応

■ lambda 式 / Stream API

■ Optional

■ Date and Time API (JSR-310)

DSL.using(connection) .select( COLUMNS.TABLE_NAME, COLUMNS.COLUMN_NAME, COLUMNS.TYPE_NAME ) .from(COLUMNS) .where(COLUMNS.TABLE_NAME.equal("BOOK")) .orderBy( COLUMNS.TABLE_CATALOG, COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME, COLUMNS.ORDINAL_POSITION ) .fetch() // ここまで jOOQ .stream() // ここから Stream API

.collect(groupingBy( r -> r.getValue(COLUMNS.TABLE_NAME), LinkedHashMap::new, mapping( r -> new Column( r.getValue(COLUMNS.COLUMN_NAME), r.getValue(COLUMNS.TYPE_NAME) ), toList() ) )) .forEach( (table, columns) -> { System.out.println( "CREATE TABLE " + table + " (" ); System.out.println( columns.stream() .map(col -> " " + col.name + " " + col.type) .collect(joining(",\n")) ); System.out.println(");"); } );

+----------+-----------+---------+|TABLE_NAME|COLUMN_NAME|TYPE_NAME|+----------+-----------+---------+|BOOK |ID |INTEGER ||BOOK |TITLE |VARCHAR |+----------+-----------+---------+

CREATE TABLE BOOK ( ID INTEGER, TITLE VARCHAR);

jOOQ のいいところ

● 何よりもタイプセーフで可読性が高いこと

○ 運用フェーズにおいて、コードの可読性は大事

■ 影響調査コストや保守性に直結する

○ 時間がしばらく経っても Repository 層( Dao

層)のコードを読めば何やってるかはだいたい分

かる

運用・保守フェーズの心強い味方😂

CyberAgent, Inc. All Rights Reserved

Getting started with jOOQ

jOOQ ことはじめ

● クエリの組み立て● SQL 生成● 結果のフェッチ方法● レコードの更新

DSLContext (エントリポイント)

jOOQ では DSLContext というクラスで操作を行う

// 自動生成された DB スキーマオブジェクトを static インポート

import static org.yukung.sample.jooq.Tables.*;

DSLContext dsl = DSL.using(conn, SQLDialect.MYSQL);// AUTHOR は Tables クラスの中で定数定義されている

Result<Record> result = create.select().from(AUTHOR).fetch();

dsl.select(field("book.title"), field("author.first_name"), field("author.last_name")) .from(table("book")) .join(table("author")) .on(field("book.author_id").equal(field("author.id"))) .where(field("book.published_in").equal(1915)) .fetch();

クエリの組み立て

+----------+-----------------+----------------+|book.title|author.first_name|author.last_name|+----------+-----------------+----------------+|羅生門 |芥川 |龍之介 ||こころ |夏目 |漱石 |+----------+-----------------+----------------+

クエリの組み立て

dsl.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.equal(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.equal(1915)) .fetch();

クエリの組み立て

dsl.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.equal(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()) .fetch();

クエリの組み立て

+-----+----------+---------+|title|first_name|last_name|+-----+----------+---------+|こころ |夏目 |漱石 ||羅生門 |芥川 |龍之介 |+-----+----------+---------+

クエリの組み立て

dsl.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.equal(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch();

クエリの組み立て

String sql = dsl.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.equal(AUTHOR.ID)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .getSQL();

SQL 生成

SELECT `jooq`.`book`.`title`, `jooq`.`author`.`first_name`, `jooq`.`author`.`last_name`FROM `jooq`.`book` JOIN `jooq`.`author` ON `jooq`.`book`.`author_id` = `jooq`.`author`.`id`ORDER BY `jooq`.`book`.`title` ASCLIMIT ? OFFSET ?

SQL 生成

dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatCSV(',', "");

便利なフェッチ( CSV )

id,title,published_in,author_id1,羅生門 ,1915,1

dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatJSON();

便利なフェッチ( JSON )

{ "fields": [ { "schema": "jooq", "table": "book", "name": "id", "type": "INTEGER" }, { "schema": "jooq", "table": "book", "name": "title", "type": "VARCHAR" }, { "schema": "jooq", "table": "book", "name": "published_in", "type": "INTEGER" },

便利なフェッチ( JSON )

"records": [ [ 1, "羅生門 ", 1915, 1 ] ]}

dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatXML();

便利なフェッチ( XML )

<result xmlns="http://www.jooq.org/xsd/jooq-export-3.7.0.xsd"> <fields> <field schema="jooq" table="book" name="id" type="INTEGER"/> <field schema="jooq" table="book" name="title" type="VARCHAR"/> <field schema="jooq" table="book" name="published_in" type="INTEGER"/> <field schema="jooq" table="book" name="author_id" type="INTEGER"/> </fields> <records> <record> <value field="id">1</value> <value field="title">羅生門 </value> <value field="published_in">1915</value> <value field="author_id">1</value> </record> </records></result>

便利なフェッチ( XML )

dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatHTML();

便利なフェッチ( HTML )

<table> <thead> <tr> <th>id</th> <th>title</th> <th>published_in</th> <th>author_id</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>羅生門 </td> <td>1915</td> <td>1</td> </tr> </tbody></table>

便利なフェッチ( HTML )

dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().format();

便利なフェッチ( Text )

+----+-----+------------+---------+| id|title|published_in|author_id|+----+-----+------------+---------+| 1|羅生門 | 1915| 1|+----+-----+------------+---------+

結果のフェッチ方法

自前の POJO クラスや Map にも自然にマッピングできる

List<BookDto> books = dsl.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.equal(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetchInto(BookDto.class);

結果のフェッチ方法

自前の POJO クラスや Map にも自然にマッピングできる

Map<Integer, Book> books = dsl.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.equal(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetchMap(BOOK.ID, Book.class);

// 新しいレコードを作る

BookRecord book1 = dsl.newRecord(BOOK);book1.setTitle("JJUG CCC");book1.setPublishedIn(2015);book1.setAuthorId(2);// INSERT INTO book (title, published_in, author_id) VALUES ('JJUG CCC', 2015, 2);book1.store();

レコードの更新( Create )

BookRecord book2 = dsl.fetchOne(BOOK, BOOK.ID.equal(3));book2.setTitle("JJUG CCC 2015");// UPDATE book SET title = 'JJUG CCC 2015' WHERE id = 3;book2.store();

BookRecord book = dsl.fetchOne(BOOK, BOOK.ID.equal(3));// DELETE FROM book WHERE id = 3;book.delete();

レコードの更新( Update / Delete )

Result<BookRecord> books = dsl.fetch(BOOK);modify(books);addSomething(books);

// バッチ更新 insert/updatedsl.batchStore(books);

バッチ更新

Contents

事例紹介

Ameba プラットフォームについて

実際に遭遇したこと

システム構成

解決したいこと

要素技術

ワークフロー

Flyway

jOOQ

Flyway と jOOQ の連携

CyberAgent, Inc. All Rights Reserved

Flyway と jOOQ の連携

DB スキーマの変更を追跡したい

アプリケーションコードだけでなく、 DB スキーマもバージョン管理する

スキーマの変更管理を楽にしたい

DB スキーマの変更管理を人手で行うのではなく、自動化して常に一貫性を保つ

変更検知を静的に、かつ

スキーマとコード の乖離を防ぐ

DB スキーマの変更によるアプリケーションコードへの影響を、動作させる前に検知

これらのワークフローを一気通貫に行いたい

解決したいこと(再掲)

可読性を落とさずに、バージョン管理されたDB スキーマ情報をアプリケーションコードに活かす

ワークフローの理想

DB スキーママイグレーション

スクリプト

DB マイグレーション

Git リポジトリ schema_version

+---------+-----------------------+---------------------+---------+| Version | Description | Installed on | State |+---------+-----------------------+---------------------+---------+| 1 | << Flyway Baseline >> | 2015-11-25 14:39:19 | Baselin || 2 | Add new table | 2015-11-25 14:46:56 | Success |+---------+-----------------------+---------------------+---------+

bookauthor

SchemaImpl

Tables

BOOK

BookRecord

AUTHOR

AuthorRecord

jOOQ コード

コード生成

成果物 jar

ビルド

この流れをビルドフローに組み込む

ビルドシステムのサポート

● Flyway○ CLI (Java)○ Java API○ Ant○ Maven○ Gradle○ sbt

● jOOQ○ CLI (Java)○ Java API○ Ant / Gradle

■ Generation Tool○ Maven

■ Plugin

Gradle のタスクグラフをカスタマイズする

ビルドワークフロー( Gradle )

Clean flywayMigrate

jooqGenerate

compileJava

processResources

classes

jar

assemble

build

flyway-gradle-pluginで追加されるタスク

自前で作成したタスク

task jooqGenerate(dependsOn: 'flywayMigrate') { // configuration for jOOQ}compileJava.dependsOn jooqGenerateprocessResources.dependsOn jooqGenerate

DB スキーマの一気通貫な CI

● 以下をトリガーとして前項のビルドフローを実行することで、 DB スキーマとアプリケーションの整合性

を CI でチェックできる○ ステージング・本番環境へのビルド・デプロイ○ IntegrationTest や FunctionalTest (E2E Test)

+ ++

CyberAgent, Inc. All Rights Reserved

まとめ

DB スキーマの変更を追跡したい

アプリケーションコードだけでなく、 DB スキーマもバージョン管理する

スキーマの変更管理を楽にしたい

DB スキーマの変更管理を人手で行うのではなく、自動化して常に一貫性を保つ

変更検知を静的に、かつ

スキーマとコード の乖離を防ぐ

DB スキーマの変更によるアプリケーションコードへの影響を、動作させる前に検知

Flyway と jOOQ で、一貫性と保守性を保ちながら、現実と向き合いつつも快適な運用保守ライフを!

まとめ

可読性を落とさずに、バージョン管理された DB スキーマをアプリケーションコードに活かす

CyberAgent, Inc. All Rights Reserved

さいごに宣伝

日本語翻訳しています(非公式だけどね)

近日中に公開(できれば)したいです

頑張ります ( ˘ω˘)

手伝ってくれる方歓迎! 懇親会でも Twitter でもお気軽に!

ご清聴ありがとうございました

Recommended