More Related Content
Similar to Spring Bootをはじめる時にやるべき10のこと (20)
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