Download - Spring Bootをはじめる時にやるべき10のこと
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
皆さん用意はいいですか?
2
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
鈴木会長はこっちじゃないぞ?
3
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
では、始めましょう!
4
Copyright © Acroquest Technology Co., Ltd. All rights reserved.
自己紹介
5
• 谷本 心 (Shin Tanimoto)
- Acroquest Technology株式会社
- 開発&トラブルシュート教育
- JavaOneスピーカー
- JJUG / 関ジャバ / S2JSFコミッタ
- Twitter : @cero_t (日本語)
- Facebook : shin.tanimoto (英語)
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.6
Struts + HibernateSeasar2 + S2JSF + S2Dao
Click + Guice + MirageSpring MVC + Hibernate
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
フレームワークに求めていること
7
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
開発効率生産性
8
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
ではなくて
9
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
ハマらないミスしない
ミスをリカバリできる
10
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
ひとつハマれば3人日が奪われ
11
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
ひとつミスしていれば商用障害が起き
12
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
リカバリできずに今日も徹夜
13
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
ハマらないミスしない
ミスをリカバリできる
14
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
そのためのSpring Boot
15
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.17
#1 SpringBootを知る
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
1. Spring Bootを知る
Spring Bootとは複雑化したSpringプロジェクト群を使った開発をシンプルに開始できる仕組み
18
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.19
Springベースのフルスタックプラットフォーム
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
1. Spring Bootを知る
フルスタックプラットフォームView層、コンテナ層、データアクセス層監視、非同期メッセージング、クラウド対応など様々な機能を「Spring Boot」という共通のプラットフォーム上で利用できる。
20
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
1. Spring Bootを知る
フルスタックプラットフォームでないと自分で様々なフレームワークを組み合わせるともちろん自由に選べる反面、設定の記述や、動作検証などが必要になる。ここに「ハマり」要因がある。
21
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.23
#1Spring Bootなら
組み合わせでハマらない!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.24
#2 はじめての
Spring Boot
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
2. はじめてのSpring Boot
新しいプロダクトの利用時あるあるドキュメントがない。ドキュメントが英語しかない。ブログがない。ノウハウがない。とりあえず少しずつググりながら場当たり対応で何とか凌ぐ。そんなことをしているから「設計ミス」をする。
25
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.27
#2はじめてのSpring Bootで初期学習を効率化!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.28
#3 Spring Initializr
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
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
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.32
#3Spring Initializrなら初期構築でミスしない!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.33
#4 pom.xmlの設計
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
4. pom.xmlの設計
pom.xmlのグッドプラクティスデプロイ単位、ライフサイクル単位で分割する共通部分を切り出すフレームワーク部分自動生成したエンティティ
しかしそうすると、前述したJARのバージョン違い問題が起きる・・・?
35
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.37
#4Spring IO platformで
pom.xmlをシンプルに!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.38
#5 Controllerの共通設定
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
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
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
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
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
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クラスも使えるようにしておく
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
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埋め、区切りを少し自由に
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.47
#5ControllerでLocalDateを使って日付判定ミスを防ぐ!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.48
#6 トランザクション境界の設定
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
6. トランザクション境界の設定
トランザクションあるあるControllerをトランザクション境界にする処理の途中で一度コミットしたいけどできない非同期処理呼び出しの前にコミットとか
途中でコミットできるような仕組みを無理に作ったら全体的な整合性が崩れた
性能改善のためのリードレプリカを作りたいが、 そもそもアクセスを振り分けられない
49
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
6. トランザクション境界の設定
Service層をトランザクション境界にするControllerの次の層をトランザクション境界にする処理の単位が明確になるService層からreturnすればコミット、例外で戻ればロールバック。
Service層のクラスすべてに@Transactionalアノテーションをつける
50
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
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
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.54
#6ServiceクラスにRead Onlyなトランザクションを設けて
将来に備える!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.55
#7 O/Rマッパーの選択
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
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
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.59
そこでBootiful SQL Template
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
7. O/Rマッパーの選択
Bootiful SQL TemplateJdbcTemplate / NamedParameterJdbcTemplateの 独自ラッパー
SQLファイルを書ける(FreeMarker形式も可)
モダンなAPI
publicフィールドが使える
JSR 310に対応(ZonedDateTimeにも)
https://github.com/cero-t/sqltemplate
60
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
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ファイルにジャンプ
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
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を自動的に消す機能が なくてごめんな
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.65
#7Bootiful SQL TemplateでSQLを書いてハマり知らず
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.66
#8 例外処理の共通化
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
8. 例外処理の共通化
例外処理あるある個別の開発者任せもちろん死ぬ
フレームワークが返す例外(バリデーションなど)と、 アプリケーションが返す例外でJSONの形式が違うクライアント側で判別に失敗して死ぬ
個別のエラーコードを定義しておいたので 全部ソースコード内に書く守らない人がいて死ぬ
67
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
8. 例外処理の共通化
例外処理は共通化するApplicationException extends RuntimeExceptionを作って必ずこれを使うエラー種別をenumに定義しておき、ApplicationExceptionのコンストラクタの第一引数に必ず渡すエラー種別と、HTTPステータスやエラーコード、 エラーメッセージを紐付けて管理する
68
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
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
第一引数でエラー種別を受け取る
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
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
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
エラーを列挙
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
8. 例外処理の共通化
例外処理ハンドリングも共通化するApplicationExceptionとRuntimeExceptionとフレームワークが返す例外ですべて同じ例外ハンドリングをする
74
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
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の両方を同じ方式でハンドリング
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
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
例外オブジェクトへの変換
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
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が投げる例外をハンドリングできる
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.81
#8すべての例外を共通してハンドリングせよ!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.82
#9 AOPによるロギング
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
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
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 / ServiceDaoの実行前後に処理を差し込む
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.86
#9AOPによるロギングで問題発生時のリカバリを
高速化しよう
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.87
#10 テスト設計
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
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
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
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はあとで。
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
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を設定
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
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
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
本体コード側にあるインタフェース。
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時にはエラーが起きる
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
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を書けばこのコンポーネントが優先して使われる。
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
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を明示的に読み込む
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.103
まとめ
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.104
スライド読み直せ!
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.105
One more thing…
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.106
今日紹介したソースはGitHubで公開中!
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