jOOQ と Flyway で立ち向かう、自社サービスの保守運用 #jjug_ccc #ccc_ab1
-
Upload
yusuke-ikeda -
Category
Engineering
-
view
10.475 -
download
0
Transcript of jOOQ と Flyway で立ち向かう、自社サービスの保守運用 #jjug_ccc #ccc_ab1
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 プラットフォーム
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 、動くけど使ってない…?
コミットログ見てみるか…
CyberAgent, Inc. All Rights Reserved
事案 4
とある DAO クラスを
眺めていて
これはつらい…
何が『つらい』?
もう一度
事案 4
● 変更による影響が拾えない
● typo していても動かしてみないと気づかない
● JPA なら Criteria API 、 MyBatis なら Generator の
Criteria を使えばタイプセーフにはできるけど、抽象
度が上がってコードの可読性が下がる
つらいところ
文字列で記述されている
DB スキーマがコードに埋め込まれている
実際に動かさないとわからない
MyBatis の Criteria の例
事案 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 でもお気軽に!
ご清聴ありがとうございました