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

10,505 views

Published on

JJUG CCC 2015 Fall の発表資料です。
http://www.java-users.jp/?page_id=2064#AB1

Published in: Engineering
0 Comments
14 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
10,505
On SlideShare
0
From Embeds
0
Number of Embeds
3,947
Actions
Shares
0
Downloads
35
Comments
0
Likes
14
Embeds 0
No embeds

No notes for slide

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

  1. 1. CyberAgent, Inc. All Rights Reserved JJUG CCC Fall 2015 #jjug_ccc #ccc_ab1 jOOQ と Flyway で立ち向かう、 自社サービスの保守運用(仮) Yusuke Ikeda / @yukung CyberAgent, Inc.
  2. 2. ● 池田 裕介(@yukung) ● 株式会社サイバーエージェント 技術本部 ● サーバサイドエンジニア ● Ameba プラットフォームのAPI設計・運用 Java や Groovy の コミュニティに よく出没します About me
  3. 3. https://www.cyberagent.co.jp/techinfo_detail/id=11016&season=2015&category=sever Spring in Summer で発表しました
  4. 4. CyberAgent, Inc. All Rights Reserved 突然ですが、質問です
  5. 5. 今、何かのサービスや システムの 保守をしていますか?
  6. 6. ソースコードの バージョン管理は していますか?
  7. 7. ユニットテストは 書いていますか?
  8. 8. CI は 回っていますか?
  9. 9. データベースの スキーマも バージョン管理 していますか?
  10. 10. CyberAgent, Inc. All Rights Reserved 本日のゴール
  11. 11. 本日のゴール アプリだけでなく DB もセットで バージョン管理+ CI して、 「つらくない」😁 快適な運用保守ライフを送ろう!
  12. 12. そう、 + ならね。
  13. 13. データベーススキーマのバージョン管理 データベーススキーマと アプリケーションコードとの乖離 保守フェーズで抱える悩み
  14. 14. 想定しているシチュエーション 運用・保守フェーズ 新規開発フェーズや、市場調査のための試行錯誤・技術検証フェーズでは 参考にならないかもしれません。 既存の DB スキーマが存在 DB スキーマを開発者が設計に関わることができ、DB スキーマの正規化が 有効になされているプロジェクトでは、必ずしも効果的ではないかもしれません。 DB スキーマとコードを効果的に管理できていない フレームワークが提供している DB マイグレーションの仕組みなどを用いて、 既に DB マイグレーションを開発プロセスとして取り入れている人には、当たり前の話です。
  15. 15. Contents 事例 紹介 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携 Ameba プラットフォーム について
  16. 16. Contents 事例 紹介 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携 Ameba プラットフォーム について
  17. 17. コミュニティ+ゲーム+メディアの プラットフォーム 2012年4月ローンチ 現時点で延べ300サービス 会員数:約3,900万 月間PV:約144億 月間投稿数:約3,000万 Ameba プラットフォーム
  18. 18. Ameba のサービス提供フロー ディベロッパー App App App 2.開発Ameba Developer Center 1.登録 3.申請 プラットフォームで提供するサービスの 情報を集約・管理し、審査や公開を行う 5. 公開 認証 システム 課金 システム 分析 クラスタ ソーシャル グラフAPI Ameba プラットフォーム 4. データ連携
  19. 19. Contents 事例 紹介 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携 Ameba プラットフォーム について
  20. 20. Architecture 社内 ログ解析基盤 Patriot Database developer.amebame.com Mail Server admin.developer.amebame.com Backend service (Batch, etc) Reverse Proxy SPA + REST API
  21. 21. CyberAgent, Inc. All Rights Reserved いたって普通の サーバサイドアーキテクチャです
  22. 22. Contents 事例 紹介 Ameba プラットフォーム について 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携
  23. 23. CyberAgent, Inc. All Rights Reserved 事案 1
  24. 24. 既存の仕様を 調べてたら…
  25. 25. よーしパパ MySQL 調べちゃうぞ〜 mysql> DESC hoge_table; っと… あれっ!?このカラム、 ステージングにはあるけど 本番環境には無いぞ??
  26. 26. しょうがない、 定義書の変更履歴見てみるか…
  27. 27. 事案 1 必要なのかどうか 分からない ある項目が いつ何のために 追加されたのか わからない 影響が怖いので 触りたくない デッドコードが増える → リファクタリングしにくい → 保守性↓↓ 🙅
  28. 28. CyberAgent, Inc. All Rights Reserved 事案 2
  29. 29. あれっ…Aさん、なんか定義書に無い テーブルが存在してるんですが… あぁ…それ定義書に反映してないんだ と思うよー、最新にしといてー
  30. 30. クッ…めんどくさ… DDLから リバースエンジニアリングするか… 時間とともに秘伝のタレとなっていく → 定義書 is 何 事案 2
  31. 31. CyberAgent, Inc. All Rights Reserved 事案 3
  32. 32. この SQL、動くけど使ってない…? コミットログ見てみるか…
  33. 33. CyberAgent, Inc. All Rights Reserved 事案 4
  34. 34. とある DAO クラスを 眺めていて
  35. 35. これはつらい…
  36. 36. 何が『つらい』?
  37. 37. もう一度
  38. 38. 事案 4 ● 変更による影響が拾えない ● typo していても動かしてみないと気づかない ● JPA なら Criteria API、MyBatis なら Generator の Criteria を使えばタイプセーフにはできるけど、抽象度 が上がってコードの可読性が下がる つらいところ 文字列で記述 されている DB スキーマがコードに 埋め込まれている 実際に動かさないと わからない
  39. 39. MyBatis の Criteria の例
  40. 40. 事案 4 ● DB スキーマ変更の影響がエラーで検知できない ○ コード中に DB スキーマ情報が埋め込まれている ○ クエリが XML やプロパティファイルに記述してあ っても同じ ● 自動化されたユニットテストと CI 必須 つらいところ 文字列で記述 されている DB スキーマがコードに 埋め込まれている 実際に動かさないと わからない
  41. 41. 事案 4 ● SQL の文法エラーですら拾えない ○ 自動化されたユニットテストと CI 必須 ○ 保守コストは高い ● DB スキーマの情報と、アプリケーションコードが地続 きになっていない ○ DB スキーマの変更も動かす前に検知したい つらいところ 文字列で記述 されている DB スキーマがコードに 埋め込まれている 実際に動かさないと わからない
  42. 42. DB スキーマがコードに 埋め込まれている 事案 4 文字列で記述 されている 実際に動かさないと わからない つらくない開発を!もっと! 変更を「検知しやすく」、 可読性が高く「理解しやすい」状態 を保つ仕組みが欲しい
  43. 43. Contents 事例 紹介 Ameba プラットフォーム について 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携
  44. 44. DB スキーマの変更 を追跡したい アプリケーションコードだけでなく、DB スキーマも バージョン管理する スキーマの変更管 理を楽にしたい DB スキーマの変更管理を人手で行うのではなく、 自動化して常に一貫性を保つ 変更検知を 静的に、かつ スキーマとコード の乖離を防ぐ DB スキーマの変更によるアプリケーションコード への影響を、動作させる前に検知 これらのワークフローを一気通貫に行いたい 解決したいこと 可読性を落とさずに、バージョン管理された DB スキーマ情報をアプリケーションコードに活かす
  45. 45. DB スキーマの変更 を追跡したい スキーマの変更管 理を楽にしたい 変更検知を 静的に、かつ スキーマとコード の乖離を防ぐ 解決したいこと データベーススキーマのバージョン管 理 データベーススキーマと アプリケーションコードとの乖離
  46. 46. DB スキーマの変更 を追跡したい スキーマの変更管 理を楽にしたい 変更検知を 静的に、かつ スキーマとコード の乖離を防ぐ 解決したいこと Flyway jOOQ
  47. 47. Contents 事例 紹介 Ameba プラットフォーム について 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携
  48. 48. CyberAgent, Inc. All Rights Reserved Flyway
  49. 49. Flyway とは ● DB マイグレーションツール ○ DB スキーマの構成管理を手軽に自動化できる ○ DDL や DML をマイグレーションスクリプトとして記述する ● Ant や Maven, Gradle などのビルドツールと一緒に利用するとよい ○ アプリケーションのビルドに DB スキーマの構成管理を統合できる ● CLI や Java API からでも使える ○ サーバサイドだけでなく Android の SQLite でも使える ● Git などのバージョン管理システムの管理下に置くことで、DB スキーマ もコードで表現できる(Infrastructure as Code に似た考え方)
  50. 50. CyberAgent, Inc. All Rights Reserved Flyway のセットアップ
  51. 51. 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 のセットアップ
  52. 52. create table PERSON ( ID int not null, NAME varchar(100) not null ); DDL をそのまま記述できる。 マイグレーションスクリプト
  53. 53. デフォルトでは以下の命名規則にしたがって記述する。 設定で変更することもできる。 スクリプトファイルの命名規則 V2__Add_new_table.sql プレフィックス バージョン ドット(.)もしくはア ンダースコア(_)で 句切られた数字 セパレータ アンダースコア2つ(__) 説明 バージョン管理用テーブル に記録される サフィックス
  54. 54. デフォルトでは以下のようなファイル配置とする。 設定で変更することもできる。 スクリプトファイルの配置 プロジェクトルート src main java db migration resources V0.2__Add_new_table.sql
  55. 55. CyberAgent, Inc. All Rights Reserved Flyway で マイグレーション
  56. 56. baseline コマンドを使う(init は flyway 4.0 で削除予定) $ ./gradlew flywayBaseline -i :flywayBaseline (Thread[Daemon worker Thread 2,5,main]) started. :flywayBaseline Executing 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. スキーマ管理テーブルの作成
  57. 57. スキーマ管理テーブルの作成 マイグレーションの適用状態を管理する “schema_version” というテーブルが作成される。
  58. 58. info コマンドを使う。 $ ./gradlew flywayInfo -i :flywayInfo (Thread[Daemon worker,5,main]) started. :flywayInfo Executing 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” なのでまだ適用されていない
  59. 59. migrate コマンドを使う。 $ ./gradlew flywayMigrate -i :flywayMigrate (Thread[Daemon worker Thread 2,5,main]) started. :flywayMigrate Executing 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": 1 Migrating schema "PUBLIC" to version 2 - Add new table Successfully 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 のスクリプトが 適用された
  60. 60. マイグレーションスクリプトが適用され、 バージョンが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” になった
  61. 61. 再度、状態を確認 PERSON テーブルが作成されている。
  62. 62. 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 の状態と 齟齬がある内容に変更
  63. 63. 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 マイグレーションの状態を検証する スクリプトが チェックサムエラーになる
  64. 64. repair コマンドを使い、マイグレーション情報を削除して チェックサムを最新に更新 $ ./gradlew flywayRepair -i :flywayRepair (Thread[Daemon worker Thread 3,5,main]) started. :flywayRepair Executing 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. マイグレーションを修復する チェックサムが更新され 修復される
  65. 65. clean コマンドを使うと、スキーマが全て削除される (やり直しはできないので、バックアップ必須) $ ./gradlew flywayClean -i :flywayClean (Thread[Daemon worker Thread 4,5,main]) started. :flywayClean Executing 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. スキーマを削除する
  66. 66. CyberAgent, Inc. All Rights Reserved Flyway の留意点
  67. 67. 巻き戻しはサポート外 ● 既存データの取扱いや、参照整合性制約など考慮すべきことが多い ● 運用しているデータベースについては、スナップショットからリストア する方が安全
  68. 68. 複数環境や、並行開発への対応 ● 特定のバージョンまで適用したい ○ target オプションを使ってバージョンを指定する ○ 運用環境でデプロイ時に指定するのは事故のもと ● ブランチを切って並行に開発している場合のバージョンの衝突 ○ Flyway は適用されているバージョンより古いものは適用されない ○ outOfOrder オプションを true にすると、古いバージョンも適用 される ○ ファイル名を数字ではなくタイムスタンプにすると良い ■ V20151125114035__Add_age_column.sql
  69. 69. Contents 事例 紹介 Ameba プラットフォーム について 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携
  70. 70. CyberAgent, Inc. All Rights Reserved jOOQ
  71. 71. jOOQ とは ● SQL を Java コードで DSL で表現して DB アクセスできるライブラリ ● JPA のように DB へのアクセスを抽象化するのではなく、できるだけ SQL をそのままアプリケーションコードとして表現することにこだわる ● 裏側で何をやってるかを隠すのではなく、コード上から何をやっている かがわかるように ● 読み方は “joke” らしい(海外の人の発音を聞くと「ジューク」の方が近 い?) ○ https://groups.google.com/forum/#!msg/jooq-user/SGG7J5ulVBs/1l3XTtSVu9AJ
  72. 72. 百聞は一見にしかず
  73. 73. 著者の誕生日が 1920 年以降で、名前が Paulo さんの書籍 一覧を取得する SQL(本のタイトル昇順) SELECT * FROM author a JOIN book b ON a.id = b.author_id WHERE a.year_of_birth > 1920 AND a.first_name = 'Paulo' ORDER BY b.title サンプル
  74. 74. 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(); サンプル
  75. 75. CyberAgent, Inc. All Rights Reserved jOOQ の特徴
  76. 76. jOOQ の特徴 ● Database First ○ 既存の DB スキーマを重視 ■ レガシーなシステムの DB スキーマにも対応 ○ 世の中、必ずしもキレイに正規化されたスキーマ だけじゃないですよね? ○ 既存のふざけた DB スキーマに悪態つきつつも、 現実と戦うことは多いはず この DB スキーマふざけてるな!! 誰だ設計したのは!!💢💢 レガシーDB
  77. 77. jOOQ の特徴 ● Database First ○ 逆に言うと、以下の様な状況なら JPA の方が生産性は高いと思いま す ■ 自分たちで DB スキーマをコントロールできる ■ きちんと正規化されているスキーマを相手にできる ○ クエリの自動生成機能などはなく、自分でガリガリクエリを書く必 要がある ■ 生産性の高さは謳っていない ■ 一応、DAO クラスを生成ツールで自動生成できる ● 単純な CRUD ならそれを使うと実装しなくてもよい
  78. 78. ● 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 の補完がガシガシ利く
  79. 79. jOOQ の特徴 ● Code Generation ○ DB スキーマのメタデータから Java オブジェクト を生成(生成ツールが付属) ○ DB スキーマの変更は再生成することで反映 ○ 一貫性がなければコンパイルエラーとして検知 ○ DB スキーマとアプリケーションコードが地続きに
  80. 80. jOOQ の特徴 ● Active Records ○ 行は自動生成されたクラスのインスタンスにマッ ピング ○ 自前の POJO にもそのままマッピングできる
  81. 81. jOOQ の特徴 ● 他にも ○ Multi-Tenancy ■ 複数スキーマや共有スキーマに対応 ○ Standardisation ■ RDBMS ごとの方言の差異を DSL で吸収 ● などなど…
  82. 82. CyberAgent, Inc. All Rights Reserved jOOQ のいいところ
  83. 83. jOOQ のいいところ ● ドキュメントやチュートリアル、サンプルコードが充 実している ○ 公式ドキュメントがすごい(英語) ○ ビデオチュートリアルや各種フレームワーク/ラ イブラリとの連携サンプルコードもたくさん ● Users Group や Stack Overflow も活発に回答が ● 商用ライセンスもある
  84. 84. jOOQ のいいところ ● 依存ライブラリが少ない ○ 標準では依存ライブラリなし ○ 設定により特定の機能を有効にすることで幾つか のライブラリに依存する
  85. 85. $ ./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
  86. 86. jOOQ のいいところ ● 後方互換性も大事にされており、活発に開発されてい る ○ v3.7 で Java8 に正式対応 ■ lambda 式 / Stream API ■ Optional ■ Date and Time API (JSR-310)
  87. 87. 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 );
  88. 88. jOOQ のいいところ ● 何よりもタイプセーフで可読性が高いこと ○ 運用フェーズにおいて、コードの可読性は大事 ■ 影響調査コストや保守性に直結する ○ 時間がしばらく経っても Repository 層(Dao 層) のコードを読めば何やってるかはだいたい分かる 運用・保守フェーズの心強い味方 😂
  89. 89. CyberAgent, Inc. All Rights Reserved Getting started with jOOQ
  90. 90. jOOQ ことはじめ ● クエリの組み立て ● SQL 生成 ● 結果のフェッチ方法 ● レコードの更新
  91. 91. 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();
  92. 92. 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(); クエリの組み立て
  93. 93. +----------+-----------------+----------------+ |book.title|author.first_name|author.last_name| +----------+-----------------+----------------+ |羅生門 |芥川 |龍之介 | |こころ |夏目 |漱石 | +----------+-----------------+----------------+ クエリの組み立て
  94. 94. 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(); クエリの組み立て
  95. 95. 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(); クエリの組み立て
  96. 96. +-----+----------+---------+ |title|first_name|last_name| +-----+----------+---------+ |こころ |夏目 |漱石 | |羅生門 |芥川 |龍之介 | +-----+----------+---------+ クエリの組み立て
  97. 97. 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(); クエリの組み立て
  98. 98. 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 生成
  99. 99. 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` ASC LIMIT ? OFFSET ? SQL 生成
  100. 100. 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_id 1,羅生門,1915,1
  101. 101. dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatJSON(); 便利なフェッチ(JSON)
  102. 102. { "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 ] ] }
  103. 103. dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatXML(); 便利なフェッチ(XML)
  104. 104. <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)
  105. 105. dsl.selectFrom(BOOK) .where(BOOK.PUBLISHED_IN.equal(1915)) .orderBy(BOOK.TITLE.asc()).limit(3).offset(1) .fetch().formatHTML(); 便利なフェッチ(HTML)
  106. 106. <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)
  107. 107. 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| +----+-----+------------+---------+
  108. 108. 結果のフェッチ方法 自前の 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);
  109. 109. 結果のフェッチ方法 自前の 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);
  110. 110. // 新しいレコードを作る 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)
  111. 111. 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)
  112. 112. Result<BookRecord> books = dsl.fetch(BOOK); modify(books); addSomething(books); // バッチ更新 insert/update dsl.batchStore(books); バッチ更新
  113. 113. Contents 事例 紹介 Ameba プラットフォーム について 実際に遭遇したこと システム構成 解決したいこと 要素 技術 ワーク フロー Flyway jOOQ Flyway と jOOQ の連携
  114. 114. CyberAgent, Inc. All Rights Reserved Flyway と jOOQ の連携
  115. 115. DB スキーマの変更 を追跡したい アプリケーションコードだけでなく、DB スキーマも バージョン管理する スキーマの変更管 理を楽にしたい DB スキーマの変更管理を人手で行うのではなく、 自動化して常に一貫性を保つ 変更検知を 静的に、かつ スキーマとコード の乖離を防ぐ DB スキーマの変更によるアプリケーションコード への影響を、動作させる前に検知 これらのワークフローを一気通貫に行いたい 解決したいこと(再掲) 可読性を落とさずに、バージョン管理された DB スキーマ情報をアプリケーションコードに活かす
  116. 116. ワークフローの理想 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 | +---------+-----------------------+---------------------+---------+ book author SchemaImpl Tables BOOK BookRecord AUTHOR AuthorRecord jOOQ コード コード生成 成果物 jar ビルド この流れをビルドフローに組み込む
  117. 117. ビルドシステムのサポート ● Flyway ○ CLI (Java) ○ Java API ○ Ant ○ Maven ○ Gradle ○ sbt ● jOOQ ○ CLI (Java) ○ Java API ○ Ant / Gradle ■ Generation Tool ○ Maven ■ Plugin
  118. 118. Gradle のタスクグラフをカスタマイズする ビルドワークフロー(Gradle) Clean flyway Migrate jooq Generate compileJava process Resources classes jar assemble build flyway-gradle-plugin で追加されるタスク 自前で作成した タスク task jooqGenerate(dependsOn: 'flywayMigrate') { // configuration for jOOQ } compileJava.dependsOn jooqGenerate processResources.dependsOn jooqGenerate
  119. 119. DB スキーマの一気通貫な CI ● 以下をトリガーとして前項のビルドフローを実行する ことで、DB スキーマとアプリケーションの整合性を CI でチェックできる ○ ステージング・本番環境へのビルド・デプロイ ○ IntegrationTest や FunctionalTest (E2E Test) + ++
  120. 120. CyberAgent, Inc. All Rights Reserved まとめ
  121. 121. DB スキーマの変更 を追跡したい アプリケーションコードだけでなく、DB スキーマも バージョン管理する スキーマの変更管 理を楽にしたい DB スキーマの変更管理を人手で行うのではなく、 自動化して常に一貫性を保つ 変更検知を 静的に、かつ スキーマとコード の乖離を防ぐ DB スキーマの変更によるアプリケーションコード への影響を、動作させる前に検知 Flyway と jOOQ で、一貫性と保守性を保ちながら、 現実と向き合いつつも快適な運用保守ライフを! まとめ 可読性を落とさずに、バージョン管理された DB スキーマをアプリケーションコードに活かす
  122. 122. CyberAgent, Inc. All Rights Reserved さいごに宣伝
  123. 123. 日本語翻訳しています (非公式だけどね)
  124. 124. 近日中に公開 (できれば)したいです 頑張ります( ˘ω˘)
  125. 125. 手伝ってくれる方 歓迎! 懇親会でも Twitter でもお気軽に!
  126. 126. ご清聴 ありがとうございました

×