Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Spring Bootをはじめる時にやるべき10のこと

30,892 views

Published on

Spring in Summer発表資料 (2015年8月28日)

Published in: Technology
  • Be the first to comment

Spring Bootをはじめる時にやるべき10のこと

  1. 1. Spring Bootをはじめる時に
 やるべき10のこと
 #bootきのこ Shin Tanimoto
 Acroquest Technology Co., LTD
  2. 2. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 皆さん
 用意はいいですか? 2
  3. 3. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 鈴木会長は
 こっちじゃないぞ? 3
  4. 4. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. では、始めましょう! 4
  5. 5. Copyright © Acroquest Technology Co., Ltd. All rights reserved. 自己紹介 5 • 谷本 心 (Shin Tanimoto) - Acroquest Technology株式会社 - 開発&トラブルシュート教育 - JavaOneスピーカー - JJUG / 関ジャバ / S2JSFコミッタ - Twitter : @cero_t (日本語) - Facebook : shin.tanimoto (英語)
  6. 6. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6 Struts + Hibernate Seasar2 + S2JSF + S2Dao Click + Guice + Mirage Spring MVC + Hibernate
  7. 7. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. フレームワークに
 求めていること 7
  8. 8. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 開発効率
 生産性 8
  9. 9. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ではなくて 9
  10. 10. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ハマらない
 ミスしない
 ミスをリカバリできる 10
  11. 11. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ひとつハマれば
 3人日が奪われ 11
  12. 12. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ひとつミスしていれば
 商用障害が起き 12
  13. 13. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. リカバリできずに
 今日も徹夜 13
  14. 14. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ハマらない
 ミスしない
 ミスをリカバリできる 14
  15. 15. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. そのための
 Spring Boot 15
  16. 16. Spring Bootをはじめる時に
 やるべき10のこと
 #bootきのこ Shin Tanimoto
 Acroquest Technology Co., LTD
  17. 17. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 17 #1
 SpringBootを知る
  18. 18. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 1. Spring Bootを知る Spring Bootとは 複雑化したSpringプロジェクト群を使った開発を シンプルに開始できる仕組み 18
  19. 19. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 19 Springベースの
 フルスタック
 プラットフォーム
  20. 20. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 1. Spring Bootを知る フルスタックプラットフォーム View層、コンテナ層、データアクセス層
 監視、非同期メッセージング、クラウド対応など
 様々な機能を「Spring Boot」という共通の
 プラットフォーム上で利用できる。 20
  21. 21. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 1. Spring Bootを知る フルスタックプラットフォームでないと 自分で様々なフレームワークを組み合わせると
 もちろん自由に選べる反面、
 設定の記述や、動作検証などが必要になる。 ここに「ハマり」要因がある。 21
  22. 22. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 1. Spring Bootを知る あまり強調しない方が良いこと Microservices向けフレームワーク 別にそれが目的ではない Executable JAR ≠ Microservices XMLを書かない ymlや設定クラスを少し作る 22
  23. 23. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 23 #1 Spring Bootなら
 組み合わせでハマらない!
  24. 24. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 24 #2
 はじめての
 Spring Boot
  25. 25. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 2. はじめてのSpring Boot 新しいプロダクトの利用時あるある ドキュメントがない。 ドキュメントが英語しかない。 ブログがない。ノウハウがない。 とりあえず少しずつググりながら
 場当たり対応で何とか凌ぐ。 そんなことをしているから「設計ミス」をする。 25
  26. 26. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 2. はじめてのSpring Boot はじめてのSpring Boot 26 @makingの力作 Spring Bootを用いた
 開発、試験、デプロイなどを
 まるっと習得できる こっちの入門スライドもオススメ
 http://www.slideshare.net/ makingx/grails-30-spring-boot
  27. 27. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 27 #2 はじめてのSpring Bootで
 初期学習を効率化!
  28. 28. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 28 #3
 Spring Initializr
  29. 29. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 3. Spring Initializr プロジェクト立ち上げ時あるある pom.xmlを書いて、必要な依存ライブラリを列挙する。 ・・・のは面倒だから、exampleプロジェクトを探して
 不要なソースコードを削除する。 なんか不要なJARが混入している なぜかバージョン違いのJARが混入する 頑張ってビルドファイルを書いたけど、なぜか動かない。 ここに「ミス」と「ハマり」要因がある。 29
  30. 30. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 3. Spring Initializr Spring Initializrとは https://start.spring.io/ Spring Bootプロジェクトの雛形を作る Maven or Gradleのプロジェクト作成 利用するプロジェクトやライブラリを選択できる Web、JDBC、Security、AOP、
 JPA、Thymeleafなど つまずかずにスタートできる! 30
  31. 31. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 3. Spring Initializr 使い方 https://start.spring.io/ に行く。 必要な情報と、利用するモジュールを選ぶ。 Actuatorおすすめ Generate Projectをクリック。 ダウンロードしたzipを解凍する。 IDEにインポートする。 以下のいずれかで実行する。 mainメソッドを実行する mvn spring-boot:run 31
  32. 32. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 32 #3 Spring Initializrなら
 初期構築でミスしない!
  33. 33. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 33 #4
 pom.xmlの設計
  34. 34. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 4. pom.xmlの設計 pom.xmlあるある 複数モジュールを作る時、依存JARのバージョンを
 複数のpom.xmlに重複して書いている。 そしてJARのバージョン違いが発生する 親子モジュールとか難しいから、
 単一モジュールで行くぜー! WebAPIとバッチがなぜか同じJARに入ってる 34
  35. 35. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 4. pom.xmlの設計 pom.xmlのグッドプラクティス デプロイ単位、ライフサイクル単位で分割する 共通部分を切り出す フレームワーク部分 自動生成したエンティティ しかしそうすると、前述したJARの
 バージョン違い問題が起きる・・・? 35
  36. 36. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 4. pom.xmlの設計 pom.xmlのグッドプラクティス 依存JARのバージョンは、親のpom.xmlに書く <dependencyManagement> を使ってバージョンを定義する Spring IO platformを使う http://platform.spring.io/platform/ 大げさな名前だが、ただのpom.xml Spring関連モジュールだけでなく、
 著名なプロダクトも含むバージョンを規定したpom.xml commons-lang、guice、joda-time、jruby、、、 Version1.1.4では552個のモジュールのバージョンが規定されている 36
  37. 37. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 37 #4 Spring IO platformで
 pom.xmlをシンプルに!
  38. 38. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 38 #5
 Controllerの共通設定
  39. 39. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 突然ですが、
 JSR 310 Date and Time APIの
 LocalDateクラスってご存じですか? LocalDate : 日付のみ。時間なし。 LocalTime : 時間のみ。日付なし。 LocalDateTime : 日付も時間もあり。 39
  40. 40. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 java.util.DateやCalendarはハマりやすい Dateを使って比較する際、日付だけで良いのに
 余計な時分秒が入っているせいで、判定を誤る。 Calendarで時分秒に0を指定したけど
 ミリ秒に余計な値が入っていて、判定を誤る。 SimpleDateFormatがスレッドセーフでないことを
 知らずに、商用環境で問題が起きる。 40
  41. 41. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 JSR 310をSpring Bootでも使いたい できること yyyy-MM-dd形式の日付 JSONリクエスト → LocalDateフィールド できないこと yyyy/MM/dd形式の日付 LocalDateフィールド → JSONレスポンス 41
  42. 42. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 ControllerでLocalDateを使う Controllerで利用するJacksonをカスタマイズする Controllerの引数・戻り値のオブジェクトと
 JSONリクエスト・レスポンスは、
 Jacksonでシリアライズ・デシリアライズされる。 Jackson2ObjectMapperBuilderを
 戻すメソッドに@Beanをつけることで
 カスタマイズできる。 42
  43. 43. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 @Bean public Jackson2ObjectMapperBuilder jacksonBuilder() { return Jackson2ObjectMapperBuilder.json() .indentOutput(true) .serializerByType(LocalDate.class, new JsonSerializer<LocalDate>() { @Override public void serialize(LocalDate value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeString(value.format(DATE_FORMATTER)); } }) .deserializerByType(LocalDate.class, new JsonDeserializer<LocalDate>() { @Override public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return LocalDate.parse(jp.getValueAsString(), DATE_PARSER); } }) .modules(new JSR310Module()); } 43
  44. 44. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 @Bean public Jackson2ObjectMapperBuilder jacksonBuilder() { return Jackson2ObjectMapperBuilder.json() .indentOutput(true) .serializerByType(LocalDate.class, new JsonSerializer<LocalDate>() { @Override public void serialize(LocalDate value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeString(value.format(DATE_FORMATTER)); } }) .deserializerByType(LocalDate.class, new JsonDeserializer<LocalDate>() { @Override public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return LocalDate.parse(jp.getValueAsString(), DATE_PARSER); } }) .modules(new JSR310Module()); } 44 出力するJSONを 読みやすく LocalDateの
 シリアライズと
 デシリアライズ 他のJSR 310クラスも
 使えるようにしておく
  45. 45. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 /** 日付フォーマット */ protected static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); /** 日付パースフォーマット */ protected static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(“y[-][/]M[-][/]d"); 45
  46. 46. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 5. Controllerの共通設定 /** 日付フォーマット */ protected static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); /** 日付パースフォーマット */ protected static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(“y[-][/]M[-][/]d"); 入力は柔軟に、出力は厳格に。 46 フォーマット時は
 0埋め、
 ハイフン区切り パース時は
 0埋め、区切りを
 少し自由に
  47. 47. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 47 #5 ControllerでLocalDateを
 使って日付判定ミスを防ぐ!
  48. 48. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 48 #6
 トランザクション
 境界の設定
  49. 49. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6. トランザクション境界の設定 トランザクションあるある Controllerをトランザクション境界にする 処理の途中で一度コミットしたいけどできない 非同期処理呼び出しの前にコミットとか 途中でコミットできるような仕組みを
 無理に作ったら全体的な整合性が崩れた 性能改善のためのリードレプリカを作りたいが、
 そもそもアクセスを振り分けられない 49
  50. 50. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6. トランザクション境界の設定 Service層をトランザクション境界にする Controllerの次の層をトランザクション境界にする 処理の単位が明確になる Service層からreturnすればコミット、
 例外で戻ればロールバック。 Service層のクラスすべてに
 @Transactionalアノテーションをつける 50
  51. 51. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6. トランザクション境界の設定 原則、Read Onlyトランザクションを使う Service層のクラスすべてに
 @Transactional(readOnly = true) をつける Insert / Update / Deleteが発生する時だけ
 メソッドに @Transactional(readOnly = false) を
 つける ReadOnlyトランザクションだけを
 リードレプリカに振り分ける 51
  52. 52. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6. トランザクション境界の設定 @Service @Transactional(readOnly = true) public class EmployeeService { @Autowired protected EmployeeDao employeeDao; public Employee getEmployee(Integer id) { return employeeDao.selectById(id); } @Transactional(readOnly = false) public int createEmployee(Employee employee) { return employeeDao.insert(employee); } } 52
  53. 53. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6. トランザクション境界の設定 @Service @Transactional(readOnly = true) public class EmployeeService { @Autowired protected EmployeeDao employeeDao; public Employee getEmployee(Integer id) { return employeeDao.selectById(id); } @Transactional(readOnly = false) public int createEmployee(Employee employee) { return employeeDao.insert(employee); } } 53 クラスには
 readOnly = true 更新系メソッドに
 readOnly = false
  54. 54. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 54 #6 ServiceクラスにRead Onlyな
 トランザクションを設けて
 将来に備える!
  55. 55. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 55 #7
 O/Rマッパーの選択
  56. 56. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 O/Rマッパーあるある とりあえずHibernate 思ったのと違うSQLが発行された 1リクエストでSQLが1万回発行された selectしたタイミングで一意制約違反が出た キャッシュが想定外の動きをした。 要するにハマる 56
  57. 57. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 O/Rマッパーあるある じゃぁMyBatis Spring Bootで標準対応していない 1.3で標準対応されるかも https://github.com/spring-projects/spring-boot/pull/3692 SQLを書いたXMLをフォーマットしたら
 インデントが全部消えた > とか < のエスケープ 57
  58. 58. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 O/Rマッパーあるある じゃぁJdbcTemplate なんかAPIが古くさい(Spring 2.0時代のAPI) publicフィールドが使えない JSR 310に対応していない 58
  59. 59. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 59 そこで
 Bootiful SQL Template
  60. 60. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 Bootiful SQL Template JdbcTemplate / NamedParameterJdbcTemplateの
 独自ラッパー SQLファイルを書ける(FreeMarker形式も可) モダンなAPI publicフィールドが使える JSR 310に対応(ZonedDateTimeにも) https://github.com/cero-t/sqltemplate 60
  61. 61. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 @Autowired protected SqlTemplate sqlTemplate; public List<Employee> selectByCondition(EmployeeCondition condition) { return sqlTemplate.forList("sql/EmployeeDao/selectByCondition.sql", Employee.class, condition); } 61
  62. 62. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 @Autowired protected SqlTemplate sqlTemplate; public List<Employee> selectByCondition(EmployeeCondition condition) { return sqlTemplate.forList("sql/EmployeeDao/selectByCondition.sql", Employee.class, condition); } 62 モダンでサクっと
 使えるAPI IntelliJなら
 Command(Ctrl) + クリックで
 SQLファイルにジャンプ
  63. 63. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 SELECT * FROM emp WHERE 1 = 1 <#if name??> AND ename like '%' || :name || '%' </#if> <#if hiredateFrom??> AND :hiredateFrom < hiredate </#if> <#if hiredateTo??> AND hiredate < :hiredateTo </#if> <#if deptno??> AND deptno = :deptno </#if> 63
  64. 64. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 7. O/Rマッパーの選択 SELECT * FROM emp WHERE 1 = 1 <#if name??> AND ename like '%' || :name || '%' </#if> <#if hiredateFrom??> AND :hiredateFrom < hiredate </#if> <#if hiredateTo??> AND hiredate < :hiredateTo </#if> <#if deptno??> AND deptno = :deptno </#if> 64 FreeMarker形式の
 テンプレートが利用可能
 
 ANDを自動的に消す機能が
 なくてごめんな
  65. 65. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 65 #7 Bootiful SQL Templateで
 SQLを書いてハマり知らず
  66. 66. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 66 #8
 例外処理の共通化
  67. 67. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 例外処理あるある 個別の開発者任せ もちろん死ぬ フレームワークが返す例外(バリデーションなど)と、
 アプリケーションが返す例外でJSONの形式が違う クライアント側で判別に失敗して死ぬ 個別のエラーコードを定義しておいたので
 全部ソースコード内に書く 守らない人がいて死ぬ 67
  68. 68. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 例外処理は共通化する ApplicationException extends RuntimeExceptionを 作って必ずこれを使う エラー種別をenumに定義しておき、
 ApplicationExceptionのコンストラクタの
 第一引数に必ず渡す エラー種別と、HTTPステータスやエラーコード、
 エラーメッセージを紐付けて管理する 68
  69. 69. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 public class ApplicationException extends RuntimeException { Throwable cause; Object[] args; private HttpErrors error; public AppException(HttpErrors error, Throwable cause, String... args) { super(); this.error = error; this.args = args; this.cause = cause; } // その他のコンストラクタ、cause、args、errorのgetterは割愛 public String getMessage() { if (args != null) { return "[" + error.name() + "]" + MessageFormat.format(error.getMessage(), args); } return "[" + error.name() + "]" + error.getMessage(); } } 69
  70. 70. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 public class ApplicationException extends RuntimeException { Throwable cause; Object[] args; private HttpErrors error; public AppException(HttpErrors error, Throwable cause, String... args) { super(); this.error = error; this.args = args; this.cause = cause; } // その他のコンストラクタ、cause、args、errorのgetterは割愛 public String getMessage() { if (args != null) { return "[" + error.name() + "]" + MessageFormat.format(error.getMessage(), args); } return "[" + error.name() + "]" + error.getMessage(); } } 70 第一引数で
 エラー種別を受け取る
  71. 71. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 public interface HttpErrors { /** * HTTPステータスを取得します。 * @return HTTPステータス */ HttpStatus getStatus(); /** * メッセージを取得します。 * @return メッセージ */ String getMessage(); /** * エラー名を取得します。 * @return エラー名 */ String name(); } 71
  72. 72. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 public enum Errors implements HttpErrors { USER_NOT_FOUND(HttpStatus.NOT_FOUND, "入力したIDに対応するユーザーが存在しません。userId={0}"), UNEXPECTED(HttpStatus.INTERNAL_SERVER_ERROR, "想定外のエラーが発生しました。 : {0}"); protected HttpStatus status; protected String message; Errors(HttpStatus status, String message) { this.status = status; this.message = message; } @Override public HttpStatus getStatus() { return status; } @Override public String getMessage() { return message; } } 72
  73. 73. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 public enum Errors implements HttpErrors { USER_NOT_FOUND(HttpStatus.NOT_FOUND, "入力したIDに対応するユーザーが存在しません。userId={0}"), UNEXPECTED(HttpStatus.INTERNAL_SERVER_ERROR, "想定外のエラーが発生しました。 : {0}"); protected HttpStatus status; protected String message; Errors(HttpStatus status, String message) { this.status = status; this.message = message; } @Override public HttpStatus getStatus() { return status; } @Override public String getMessage() { return message; } } 73 エラーを列挙
  74. 74. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 例外処理ハンドリングも共通化する ApplicationExceptionと
 RuntimeExceptionと
 フレームワークが返す例外で
 すべて同じ例外ハンドリングをする 74
  75. 75. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 @ControllerAdvice public class ControllerExceptionHandler extends ResponseEntityExceptionHandler { /** ロガー */ protected final Log logger = LogFactory.getLog(getClass()); @ExceptionHandler(value = ApplicationException.class) @ResponseBody public ResponseEntity<RestError> handleAppException(HttpServletRequest request, ApplicationException ex) { return handleError(request, ex.getError(), ex, ex.getArgs()); } @ExceptionHandler(value = RuntimeException.class) @ResponseBody public ResponseEntity<RestError> handleException(HttpServletRequest request, RuntimeException ex) { return handleError(request, Errors.UNEXPECTED, ex, ex.toString()); } 75
  76. 76. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 @ControllerAdvice public class ControllerExceptionHandler extends ResponseEntityExceptionHandler { /** ロガー */ protected final Log logger = LogFactory.getLog(getClass()); @ExceptionHandler(value = ApplicationException.class) @ResponseBody public ResponseEntity<RestError> handleAppException(HttpServletRequest request, ApplicationException ex) { return handleError(request, ex.getError(), ex, ex.getArgs()); } @ExceptionHandler(value = RuntimeException.class) @ResponseBody public ResponseEntity<RestError> handleException(HttpServletRequest request, RuntimeException ex) { return handleError(request, Errors.UNEXPECTED, ex, ex.toString()); } 76 独自のApplicationExceptionと
 想定しないRuntimeExceptionの両方を
 同じ方式でハンドリング
  77. 77. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 protected ResponseEntity<RestError> handleError(HttpServletRequest request, HttpErrors error, Exception ex, Object... args) { String message = MessageFormat.format(error.getMessage(), args); if (error.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR) { logger.error(message, ex); } else { logger.debug(message, ex); } if (error.getStatus() == HttpStatus.UNAUTHORIZED) { return new ResponseEntity<>(error.getStatus()); } RestError restError = new RestError(); restError.path = request.getRequestURI(); restError.error = error.name(); restError.status = error.getStatus() .value(); restError.message = message; restError.exception = ex.getClass() .getName(); return new ResponseEntity<>(restError, error.getStatus()); } 77
  78. 78. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 protected ResponseEntity<RestError> handleError(HttpServletRequest request, HttpErrors error, Exception ex, Object... args) { String message = MessageFormat.format(error.getMessage(), args); if (error.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR) { logger.error(message, ex); } else { logger.debug(message, ex); } if (error.getStatus() == HttpStatus.UNAUTHORIZED) { return new ResponseEntity<>(error.getStatus()); } RestError restError = new RestError(); restError.path = request.getRequestURI(); restError.error = error.name(); restError.status = error.getStatus() .value(); restError.message = message; restError.exception = ex.getClass() .getName(); return new ResponseEntity<>(restError, error.getStatus()); } 78 例外オブジェクトへの変換
  79. 79. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 @Override protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { RestError restError = new RestError(); if (request instanceof ServletWebRequest) { restError.path = ((ServletWebRequest) request).getRequest() .getRequestURI(); } else { restError.path = request.getContextPath(); } restError.error = status.getReasonPhrase(); restError.status = status.value(); restError.message = ex.getMessage(); restError.exception = ex.getClass() .getName(); return new ResponseEntity<>(restError, status); } } 79
  80. 80. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 @Override protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { RestError restError = new RestError(); if (request instanceof ServletWebRequest) { restError.path = ((ServletWebRequest) request).getRequest() .getRequestURI(); } else { restError.path = request.getContextPath(); } restError.error = status.getReasonPhrase(); restError.status = status.value(); restError.message = ex.getMessage(); restError.exception = ex.getClass() .getName(); return new ResponseEntity<>(restError, status); } } 80 このオーバーライドで
 Spring MVCが投げる例外を
 ハンドリングできる
  81. 81. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 81 #8 すべての例外を共通して
 ハンドリングせよ!
  82. 82. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 82 #9
 AOPによるロギング
  83. 83. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 9. AOPによるロギング AOPとは アプリケーション横断的に行う処理 トランザクションのコミットやロールバックもAOPで実現されている オススメは、AOPによる自動ロギング Controller / Service / DaoのIn/Outでロギング デバッグ時に、アプリケーションの挙動がとてもよく分かる ログレベルに応じて引数、戻り値なども出す SpringではXMLやアノテーションでAOPの対象を記述することができる 正直、アノテーションの記述はイマイチ。分かりづらい。 記述性はGuice > Seasar > Spring。 83
  84. 84. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 9. AOPによるロギング @Aspect @Component public class DumpLogInterceptor { protected Logger logger = LoggerFactory.getLogger(getClass()); @Around("execution(* ninja.cero.springboot..*.*(..)) && (bean(*Controller) || bean(*Service) || bean(*Dao))") public Object dump(ProceedingJoinPoint joinPoint) throws Throwable { try { logger.debug("BEGIN - " + toCall(joinPoint)); logger.trace("with args - " + ToStringBuilder.reflectionToString(joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE)); Object retValue = joinPoint.proceed(joinPoint.getArgs()); logger.debug("END - " + toCall(joinPoint)); logger.trace( "with return - " + ToStringBuilder.reflectionToString(retValue, ToStringStyle.SHORT_PREFIX_STYLE)); return retValue; } catch (Throwable th) { logger.debug("END throw - " + toCall(joinPoint)); logger.debug("Exception: " + th); throw th; } } } 84
  85. 85. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 9. AOPによるロギング @Aspect @Component public class DumpLogInterceptor { protected Logger logger = LoggerFactory.getLogger(getClass()); @Around("execution(* ninja.cero.springboot..*.*(..)) && (bean(*Controller) || bean(*Service) || bean(*Dao))") public Object dump(ProceedingJoinPoint joinPoint) throws Throwable { try { logger.debug("BEGIN - " + toCall(joinPoint)); logger.trace("with args - " + ToStringBuilder.reflectionToString(joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE)); Object retValue = joinPoint.proceed(joinPoint.getArgs()); logger.debug("END - " + toCall(joinPoint)); logger.trace( "with return - " + ToStringBuilder.reflectionToString(retValue, ToStringStyle.SHORT_PREFIX_STYLE)); return retValue; } catch (Throwable th) { logger.debug("END throw - " + toCall(joinPoint)); logger.debug("Exception: " + th); throw th; } } } 85 この記述でController / Service
 Daoの実行前後に処理を差し込む
  86. 86. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 86 #9 AOPによるロギングで
 問題発生時のリカバリを
 高速化しよう
  87. 87. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 87 #10
 テスト設計
  88. 88. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 テストあるある とりあえず、手動でテストしようぜ!
  → 回帰試験で死ぬ。 ロジックがあるControllerからテストすればOK!
  → バリデーションの設定ミスで死ぬ。 通常使うComponentとモックのComponentは
 @Profileで切り替えるのがSpring流!
  → 管理できなくなってきて死ぬ。 Spring Bootはend-to-endでテストしやすいから
 end-to-endのテストでカバレッジ80%を目指そう!
  → そして死ぬ。 88
  89. 89. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 テストのグッドプラクティス Controller以降をJUnitでテストする。 カバレッジは最低60%、できれば80%を目指す。 外部サービス呼び出し部分は、モック化する(後述) 例外を任意で発生させたい場合も、モック化すると良い。 無名クラスを活用したモックを作れるようになろう。 end-to-endのJUnitを書いて、
 正常系1本とバリデーション部分をテストする。 JUnitのテストクラスに @IntegrationTest(“server.port=0”) をつけ、 RestTemplateを用いてテストする。 バリデーションの試験は、end-to-endでしかできない。 89
  90. 90. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {BlankWeb.class, TestContextConfig.class}) @Transactional public class EmployeesControllerTest { @Autowired EmployeesController controller; @Test public void testGetAll() { List<EmployeesOut> employees = controller.getEmployees(); // TODO: assert } 90
  91. 91. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {BlankWeb.class, TestContextConfig.class}) @Transactional public class EmployeesControllerTest { @Autowired EmployeesController controller; @Test public void testGetAll() { List<EmployeesOut> employees = controller.getEmployees(); // TODO: assert } 91 テストの時に必ずつけるアノテーション。 トランザクションを有効にして
 試験が終わったら自動でロールバック。 TestContextConfigはあとで。
  92. 92. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {BlankWeb.class, TestContextConfig.class}) @WebAppConfiguration @IntegrationTest("server.port=0") @Transactional public class EmployeesTest { @Value("http://localhost:${local.server.port}/employees") String baseUrl; @Autowired RestTemplate restTemplate; @Test public void testGetList() { ResponseEntity<List> out = restTemplate.getForEntity(baseUrl, List.class); // TODO: assert } 92
  93. 93. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {BlankWeb.class, TestContextConfig.class}) @WebAppConfiguration @IntegrationTest("server.port=0") @Transactional public class EmployeesTest { @Value("http://localhost:${local.server.port}/employees") String baseUrl; @Autowired RestTemplate restTemplate; @Test public void testGetList() { ResponseEntity<List> out = restTemplate.getForEntity(baseUrl, List.class); // TODO: assert } 93 @WebAppConfigurationと
 @IntegrationTestをつけて
 Tomcatを起動 RestTemplateでアクセス URLを設定
  94. 94. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 テストのTips schema.sqlとdata.sqlでデータ投入する /src/test/resources にschema.sqlとdata.sqlを
 置いておけば、JUnitの起動前にのみ実行される。 /src/main/resources に置いておけば
 通常起動時に実行される(動作確認向け) 事故ってproduction環境で動かさないように! Controllerのテストと、end-to-endのテストは同じapplication.ymlでテストする end-to-endの試験も自動化するため。 end-to-endのテストは、ついつい範囲を広げたくなるが
 自動化する試験と、そうでない試験は分けるべき。 94
  95. 95. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 テストのTips @Componentのモックは、
 @Profileよりも@Primaryをつけた方が良い 外部サービスの呼び出しなどをモック化する場合、
 モック化したい部分を @Autowired にしておく。 モッククラスを /src/test/java 以下で作成し、
 @Component (@Bean) と@Primaryアノテーションを
 つければ、JUnit実行時のみ利用される。 95
  96. 96. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 public interface Context { /** * セッションIDを取得します。 * @return セッションID */ String getSessionId(); } 96 本体コード側にある
 インタフェース。
  97. 97. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 public class RequestContext implements Context { protected HttpServletRequest request; public RequestContext(HttpServletRequest request) { this.request = request; } @Override public String getSessionId() { return request.getSession().getId(); } } 97 本体コード側にある実装。
 HttpServletRequestを使っているので
 JUnit時にはエラーが起きる
  98. 98. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @Configuration public class TestContextConfig { @Bean @Primary public Context context() { return new Context() { @Override public String getSessionId() { return "JUNIT"; } }; } } 98
  99. 99. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @Configuration public class TestContextConfig { @Bean @Primary public Context context() { return new Context() { @Override public String getSessionId() { return "JUNIT"; } }; } } 99 テスト側の設定クラス。 ここに@Primaryを書けば
 このコンポーネントが
 優先して使われる。
  100. 100. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {BlankWeb.class, TestContextConfig.class}) @Transactional public class EmployeesControllerTest { @Autowired EmployeesController controller; @Test public void testGetAll() { List<EmployeesOut> employees = controller.getEmployees(); // TODO: assert } 100
  101. 101. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {BlankWeb.class, TestContextConfig.class}) @Transactional public class EmployeesControllerTest { @Autowired EmployeesController controller; @Test public void testGetAll() { List<EmployeesOut> employees = controller.getEmployees(); // TODO: assert } 101 テスト側で、先ほど書いた
 Configurationを明示的に読み込む
  102. 102. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 10. テスト設計 テストのTips /src/test/java 側に作ったテスト用の
 @Primary つき@Component (@Bean) は
 end-to-endのサーバ側にも適用されるので
 サーバ側でもモックを使いやすい たぶんこれ相当便利。 102
  103. 103. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 103 まとめ
  104. 104. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 104 スライド読み直せ!
  105. 105. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 105 One more thing…
  106. 106. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 106 今日紹介したソースは
 GitHubで公開中!
  107. 107. Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 107 https://github.com/cero-t/ spring-boot-kinoko-2015
  108. 108. Copyright © Acroquest Technology Co., Ltd. All rights reserved. 108 Enjoy Spring Boot!

×