SlideShare a Scribd company logo
Spring Bootをはじめる時に

やるべき10のこと

#bootきのこ
Shin Tanimoto

Acroquest Technology Co., LTD
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 + Hibernate
Seasar2 + S2JSF + S2Dao
Click + Guice + Mirage
Spring 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
Spring Bootをはじめる時に

やるべき10のこと

#bootきのこ
Shin Tanimoto

Acroquest Technology Co., LTD
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
#1
Spring 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
#3
Spring 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
#4
Spring 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
#5
Controllerで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
#6
Serviceクラスに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 Template
JdbcTemplate / 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
#7
Bootiful 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 / Service

Daoの実行前後に処理を差し込む
Copyright © Acroquest Technology Co., Ltd. All rights reserved.Copyright © Acroquest Technology Co., Ltd. All rights reserved.
86
#9
AOPによるロギングで

問題発生時のリカバリを

高速化しよう
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
Copyright © Acroquest Technology Co., Ltd. All rights reserved.
108
Enjoy Spring Boot!

More Related Content

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

  • 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
  • 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!