Submit Search
Spring Bootをはじめる時にやるべき10のこと
•
97 likes
•
42,247 views
心 谷本
Follow
Spring in Summer発表資料 (2015年8月28日)
Read less
Read more
1 of 108
Download now
Downloaded 250 times
More Related Content
Spring Bootをはじめる時にやるべき10のこと
1.
Spring Bootをはじめる時に やるべき10のこと #bootきのこ Shin Tanimoto Acroquest
Technology Co., LTD
2.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 皆さん 用意はいいですか? 2
3.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 鈴木会長は こっちじゃないぞ? 3
4.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. では、始めましょう! 4
5.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved. 自己紹介 5 • 谷本 心 (Shin Tanimoto) - Acroquest Technology株式会社 - 開発&トラブルシュート教育 - JavaOneスピーカー - JJUG / 関ジャバ / S2JSFコミッタ - Twitter : @cero_t (日本語) - Facebook : shin.tanimoto (英語)
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. フレームワークに 求めていること 7
8.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 開発効率 生産性 8
9.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ではなくて 9
10.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ハマらない ミスしない ミスをリカバリできる 10
11.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ひとつハマれば 3人日が奪われ 11
12.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ひとつミスしていれば 商用障害が起き 12
13.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. リカバリできずに 今日も徹夜 13
14.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. ハマらない ミスしない ミスをリカバリできる 14
15.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. そのための Spring Boot 15
16.
Spring Bootをはじめる時に やるべき10のこと #bootきのこ Shin Tanimoto Acroquest
Technology Co., LTD
17.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 17 #1 SpringBootを知る
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 19 Springベースの フルスタック プラットフォーム
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 1. Spring Bootを知る フルスタックプラットフォームでないと 自分で様々なフレームワークを組み合わせると もちろん自由に選べる反面、 設定の記述や、動作検証などが必要になる。 ここに「ハマり」要因がある。 21
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 23 #1 Spring Bootなら 組み合わせでハマらない!
24.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 24 #2 はじめての Spring Boot
25.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 2. はじめてのSpring Boot 新しいプロダクトの利用時あるある ドキュメントがない。 ドキュメントが英語しかない。 ブログがない。ノウハウがない。 とりあえず少しずつググりながら 場当たり対応で何とか凌ぐ。 そんなことをしているから「設計ミス」をする。 25
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 27 #2 はじめてのSpring Bootで 初期学習を効率化!
28.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 28 #3 Spring Initializr
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 32 #3 Spring Initializrなら 初期構築でミスしない!
33.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 33 #4 pom.xmlの設計
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 4. pom.xmlの設計 pom.xmlのグッドプラクティス デプロイ単位、ライフサイクル単位で分割する 共通部分を切り出す フレームワーク部分 自動生成したエンティティ しかしそうすると、前述したJARの バージョン違い問題が起きる・・・? 35
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 37 #4 Spring IO platformで pom.xmlをシンプルに!
38.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 38 #5 Controllerの共通設定
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.
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.
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.
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.
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.
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 47 #5 ControllerでLocalDateを 使って日付判定ミスを防ぐ!
48.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 48 #6 トランザクション 境界の設定
49.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 6. トランザクション境界の設定 トランザクションあるある Controllerをトランザクション境界にする 処理の途中で一度コミットしたいけどできない 非同期処理呼び出しの前にコミットとか 途中でコミットできるような仕組みを 無理に作ったら全体的な整合性が崩れた 性能改善のためのリードレプリカを作りたいが、 そもそもアクセスを振り分けられない 49
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.
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 54 #6 ServiceクラスにRead Onlyな トランザクションを設けて 将来に備える!
55.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 55 #7 O/Rマッパーの選択
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 59 そこで Bootiful SQL Template
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.
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.
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 65 #7 Bootiful SQL Templateで SQLを書いてハマり知らず
66.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 66 #8 例外処理の共通化
67.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 例外処理あるある 個別の開発者任せ もちろん死ぬ フレームワークが返す例外(バリデーションなど)と、 アプリケーションが返す例外でJSONの形式が違う クライアント側で判別に失敗して死ぬ 個別のエラーコードを定義しておいたので 全部ソースコード内に書く 守らない人がいて死ぬ 67
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.
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.
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.
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 8. 例外処理の共通化 例外処理ハンドリングも共通化する ApplicationExceptionと RuntimeExceptionと フレームワークが返す例外で すべて同じ例外ハンドリングをする 74
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.
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.
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.
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 81 #8 すべての例外を共通して ハンドリングせよ!
82.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 82 #9 AOPによるロギング
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 86 #9 AOPによるロギングで 問題発生時のリカバリを 高速化しよう
87.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 87 #10 テスト設計
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 103 まとめ
104.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 104 スライド読み直せ!
105.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 105 One more thing…
106.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved. 106 今日紹介したソースは GitHubで公開中!
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.
Copyright © Acroquest
Technology Co., Ltd. All rights reserved. 108 Enjoy Spring Boot!
Download