Spring Data REST
と
Spring Cloud Contract
Tagbangers, Inc.
#sf_25 #アンケート
© Tagbangers, Inc. 1
自己紹介
小川岳史
Spring Lover 10 years
山﨑大
わさもん
© Tagbangers, Inc. 2
本日のメニュー
• 背景
• Spring Data REST
• HATEOAS
• Spring Cloud Contract
• CDC
• Pact
© Tagbangers, Inc. 3
背景
• SPA 時代
• よりリッチな UI 表現のために Thymeleaf から Angular などへ
• ビジネスロジックの実装が UI 側にも必要なり…
Browser
Application • ビジネスロジック
• CRUD
Browser
Application • ビジネスロジック
• CRUD
• ビジネスロジック
DB
DB
© Tagbangers, Inc. 4
工程やチーム構成の変化
BackendFrontend
ex. Angular ex. Spring Boot
Frontend Team Backend Team
User
repo repo
CI/CDCI/CD
code
push
code
push
artifactartifact
別々のデプロイ環境
別々のチーム
別々のレポジトリ
別々のリリースサイクル
© Tagbangers, Inc. 5
どの URI にアクセスすれば
いいの?
どんなフォーマットでデー
タが取得できるの?
更新できる条件ってなんな
の??
そこで発生してきた課題
開発初期段階
© Tagbangers, Inc. 6
API の仕様が変更されて UI がバグったんだけど〜
そこで発生してきた課題
リリース後のバージョンアップで
© Tagbangers, Inc. 7
解決策
どの URI にアクセスすればいいの?
どんなフォーマットでデータが
取得できるの?
更新できる条件ってなんなの??
API の仕様が変更されて
UI がバグったんだけど〜
Spring Data REST
Spring エコシステムをフル活用!
標準化 + 自動生成 別々に進化できる仕組み
Spring Cloud Contract
Spring Data REST
✓ 標準化 + 自動生成
© Tagbangers, Inc. 9
残念な実装
class ReservationService {
Reservation getReservation(long id) {
return this.reservationRepository.findById(id);
}
Reservation createReservation(Reservation reservation) {
return this.reservationRepository.save(reservation);
}
}
© Tagbangers, Inc. 10
Spring Data REST とは
Entity
GET /items
GET /items/search
GET / items/1
POST /items
PUT /items/1
PATCH /items/1
DELETE /items/1
…
Item ItemRepository
REST スタイルな API エンドポイント
URL 設計に迷わない
Repository
© Tagbangers, Inc. 11
Spring Data REST の始め方
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
© Tagbangers, Inc. 12
Entity
Repository
@Entity
public class Order {
private Long id;
private Location location;
private LocalDateTime orderedDate;
private Status status;
}
public interface OrderRepository
extends PagingAndSortingRepository<Order, Long> {
}
© Tagbangers, Inc. 13
. ____ _ __ _ _
/ / ___'_ __ _ _(_)_ __ __ _    
( ( )___ | '_ | '_| | '_ / _` |    
/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.6.RELEASE)
...
Mapped "{[/ || ],methods=[HEAD],produces=[application/hal+json || application/json]}" ...
Mapped "{[/ || ],methods=[GET],produces=[application/hal+json || application/json]}" ...
Mapped "{[/ || ],methods=[OPTIONS],produces=[application/hal+json || application/json]}" ...
Mapped "{[/{repository}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" ...
Mapped "{[/{repository}],methods=[HEAD],produces=[application/hal+json || application/json]}" ...
Mapped "{[/{repository}],methods=[GET],produces=[application/hal+json || application/json]}" ...
Mapped "{[/{repository}],methods=[GET],produces=[application/x-spring-data-compact+json || text/uri-list]}" ...
Mapped "{[/{repository}],methods=[POST],produces=[application/hal+json || application/json]}" ...
Mapped "{[/{repository}/{id}],methods=[OPTIONS],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}],methods=[HEAD],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}],methods=[GET],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}],methods=[PUT],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}],methods=[PATCH],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}],methods=[DELETE],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}/{property}],methods=[GET],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}/{property}/{propertyId}],methods=[GET],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}/{property}],methods=[DELETE],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/{id}/{property}],methods=[GET],produces=[application/x-spring-data-compact+json || text/uri-list]}"
Mapped "{[/{repository}/{id}/{property}],methods=[PATCH || PUT || POST],consumes=[application/json || application/x-spring-data-compact+json || text/uri-list], ...
Mapped "{[/{repository}/{id}/{property}/{propertyId}],methods=[DELETE],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/search],methods=[OPTIONS],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/search],methods=[HEAD],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/search],methods=[GET],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/search/{search}],methods=[GET],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/search/{search}],methods=[GET],produces=[application/x-spring-data-compact+json]}"
Mapped "{[/{repository}/search/{search}],methods=[OPTIONS],produces=[application/hal+json || application/json]}"
Mapped "{[/{repository}/search/{search}],methods=[HEAD],produces=[application/hal+json || application/json]}"
Mapped "{[/profile],methods=[OPTIONS]}"
Mapped "{[/profile],methods=[GET]}"
Mapped "{[/profile/{repository}],methods=[GET],produces=[application/alps+json || */*]}"
Mapped "{[/profile/{repository}],methods=[OPTIONS],produces=[application/alps+json]}"
Mapped "{[/profile/{repository}],methods=[GET],produces=[application/schema+json]}"
Mapped "{[/ || ],methods=[GET],produces=[text/html]}"
Mapped "{[/browser],methods=[GET]}"
自動的にエンドポイントが登録されている
/repository…
© Tagbangers, Inc. 14
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
}
$ curl http://localhost:8080/people/1
LINK 部
DATA 部
© Tagbangers, Inc. 15
HATEOAS
• Hypermedia as the Engine of Application State
• データ部の他にリンク情報が追加され、関連するリソースをたどることができる
• Spring Data REST のリクエスト・レスポンスボディ部のフォーマットは HATEOAS に準拠
• Spring HATEOAS は独立したライブラリでこれだけ使うこともできる
© Tagbangers, Inc. 16
フロント側で ID を取り出して個別に
URL を組み立てる必要がない
ページング情報も
$ curl http://localhost:8080/people
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/people{?page,size,sort}",
"templated" : true
},
"search" : {
"href" : "http://localhost:8080/people/search"
}
},
"_embedded" : {
"persons" : [ {
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
HATEOAS にのっかることでボディ部の
フォーマットも悩まない
© Tagbangers, Inc. 17
{
"description": "iPhone",
"status": "IN_PROGRESS",
"_links": {
"self": {
"href": "http://localhost:8080/orders/4"
},
"orders": {
"href": "http://localhost:8080/orders"
},
"cancel": {
"href": "http://localhost:8080/orders/4/cancel"
},
"complete": {
"href": "http://localhost:8080/orders/4/complete"
}
}
if ( order.status == PENDING || order.status == PAID ) {
showCancelButton
}
© Tagbangers, Inc. 18
ビジネスロジックの流出を最小限
に
キャンセル判定
ロジック
キャンセル判定
ロジック
キャンセル判定
ロジック
© Tagbangers, Inc. 19
自動生成されて簡単!とはいえ…
• とにかく簡単だけど…
• Spring Data REST だけでは完成しない
• 基本的に CRUD のみ
• ビジネスロジックが必要な場合は拡張する
Controller
Service
Repository
Database
ここは自分で書く
Spring Data RESTが
よしなに
© Tagbangers, Inc. 20
Repository
Spring
Data
REST
@RepositoryRest
Controller
Service
@Controller
@RestController
Endpoint
Endpoint
Endpoint
Domain
Model
ビジネスロジックの実装パターン
EventHandler
© Tagbangers, Inc. 21
JSON Schema
• JSON Schema から TypeScript のコードの自動生成
TypescriptJson schema
自動生成
curl -H 'Accept:application/schema+json' http://localhost:8080/profile/reservations
{
"title" : "Reservation",
"properties" : {
"date" : {
"title" : "Date",
"readOnly" : false,
"type" : "string",
"format" : "date-time"
},
"name" : {
"title" : "Name",
"readOnly" : false,
"type" : "string"
}
},
"definitions" : { },
"type" : "object",
"$schema" : "http://json-schema.org/draft-04/schema#"
}
export type Date = string;
export type Name = string;
export interface Reservation {
date?: Date;
name?: Name;
[k: string]: any;
}
© Tagbangers, Inc. 22
Admin over Spring Data REST
User UI Admin UI
Spring
Data
REST
Manual
Impl
DB
© Tagbangers, Inc. 23
全文検索 over Spring Data REST
UI
Elastic
Search
Spring
Data
REST
Hibernate
Search
DB
Custom
Repository
Impl
© Tagbangers, Inc. 24
マルチテナント over Spring Data REST
UI
Spring
Data
REST
Repository DBHibernate
Spring
Security
Authentication
Event Listener
Filter
Tenant
© Tagbangers, Inc. 25
ポイント
 Spring Data REST を活用してボイラープレートコードを削減する
 REST や HATEOAS の流行りの仕様を活用する
チーム間のコミュニケーションをフォーマット的なことではなく、
よりビジネス的にフォーカスしたいポイントに絞る!!
© Tagbangers, Inc. 26
ポイント
BackendFrontend
ex. Angular ex. Spring Boot
Frontend Team Backend Team
User
repo repo
CI/CDCI/CD
code
push
code
push
artifactartifact
Spring Data REST
Spring HATEOAS
Spring Cloud Contract
でさくっと行う
Consumer Driven Contract
with Pact
別々に進化できる仕組み
© Tagbangers, Inc. 28
工程やチーム構成の変化
BackendFrontend
ex. Angular ex. Spring Boot
Frontend Team Backend Team
User
repo repo
CI/CDCI/CD
code
push
code
push
artifactartifact
別々のデプロイ環境
別々のチーム
別々のレポジトリ
別々のリリースサイクル
© Tagbangers, Inc. 29
Test Pyramid
https://martinfowler.com/bliki/TestPyramid.html
© Tagbangers, Inc. 30
モック に置き換える
HTTP
© Tagbangers, Inc. 31
モック に置き換える
その モック 意味がありますか?
• インターフェースは正しい??
• 連携するサービスが変更されたら?
• だれがメンテナンスするの?
© Tagbangers, Inc. 32
Micro service architecture
Microservice MicroserviceMicroservice
UI
Microservice
モックモックモックモックモックモックモックモック
© Tagbangers, Inc. 33
Spring Cloud Contract
Spring Cloud Contract is an
umbrella project holding
solutions that help users in
successfully implementing
the Consumer Driven
Contracts approach.
© Tagbangers, Inc. 34
Pact
Pact is a contract testing tool. Contract
testing is a way to ensure that services
(such as an API provider and a client) can
communicate with each other. Without
contract testing, the only way to know that
services can communicate is by using
expensive and brittle integration tests
© Tagbangers, Inc. 35
Contract
• 契約はインターフェース定義のようなもの
• API 仕様書の内容
• Provider が契約を守ることを約束する
Contract
Consumer
Consumer and provider are
both agreed with the sentence
below.. for example we agreed with
on the basis of the Provider’s REST
API, that is created hy
Provider
Test ✔
Test ✔
Spring Cloud Contract + Pact
で
Consumer Driven Contract
© Tagbangers, Inc. 37
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
Consumer
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
Provider
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
© Tagbangers, Inc. 38
1. テストを書く
beforeAll ((done) => {
provider = new PactWeb({
consumer: ‘sample-client-tenant',
provider: ‘sapmle-server-tenant',
port: 1234,
host: '127.0.0.1'
});
});
前準備 1
© Tagbangers, Inc. 39
1. テストを書く
beforeAll((done) => {
provider.addInteraction({
uponReceiving: 'a request for hello',
withRequest: {
method: 'GET',
path: '/hello'
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/hal+json;charset=UTF-8'
},
body: {
reply: 'Hello'
}
}
}).then(done, done.fail);
});
前準備 2
© Tagbangers, Inc. 40
1. テストを書く
it('should', (done) => {
const greetingService: GreetingService =
TestBed.get(GreetingTypeService);
greetingService.hello().subscribe(response => {
expect(response).toEqual({reply: 'Hello'});
done();
}, error => {
done.fail(error);
});
});
テスト
/hello に Get リクエスト
© Tagbangers, Inc. 41
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
Consumer
Provider
© Tagbangers, Inc. 42
2. Pact file ( Contract )
Pact file
...
"interactions": [
{
"description": "a request for hello",
"request": {
"method": "GET",
"path": "/hello"
},
"response": {
"status": 200,
"headers": {
"Content-Type":
"application/hal+json;charset=UTF-8"
...
© Tagbangers, Inc. 43
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
Contract を共有するためのアプリ
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
Consumer
Provider
© Tagbangers, Inc. 44
Pact Broker
https://github.com/pact-foundation/pact_broker
© Tagbangers, Inc. 45
Pact Broker
https://github.com/pact-foundation/pact_broker
© Tagbangers, Inc. 46
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
Consumer
Provider
© Tagbangers, Inc. 47
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
ビルド時にテストを生成 + テスト実行
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
Consumer
Provider
© Tagbangers, Inc. 48
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
Consumer
Provider
© Tagbangers, Inc. 49
CDC ステップ
1. テスト書く
2. Contract(Pact file) を自動生成
3. Pact Broker に Pact file を publish
1. Pact Broker から Contract(Pact file) を取得
2. Test を自動生成(Spring Cloud Contract )
3. モック を自動生成(Spring Cloud Contract)
成果物としてWireMockの定義ファイルができる
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
Consumer
Provider
© Tagbangers, Inc. 50
APIが実装されたか知りたい
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
Broker のステータス確認で対応状況を把握
© Tagbangers, Inc. 51
Pact Broker
https://github.com/pact-foundation/pact_broker
© Tagbangers, Inc. 52
破壊的な API の実装の変更
https://github.com/pact-foundation/pact_broker/wiki/Webhooks
テストが通らないので破壊的な変更だとわかる!
© Tagbangers, Inc. 53
使用されている API がわかる!
https://github.com/pact-foundation/pact_broker
まとめ
© Tagbangers, Inc. 55
まとめ
BackendFrontend
ex. Angular ex. Spring Boot
Frontend Team Backend Team
User
repo repo
CI/CDCI/CD
code
push
code
push
artifactartifact
Spring Data REST
Spring HATEOAS
Spring Cloud Contract

Spring data-rest-and-spring-cloud-contract

  • 1.
    Spring Data REST と SpringCloud Contract Tagbangers, Inc. #sf_25 #アンケート
  • 2.
    © Tagbangers, Inc.1 自己紹介 小川岳史 Spring Lover 10 years 山﨑大 わさもん
  • 3.
    © Tagbangers, Inc.2 本日のメニュー • 背景 • Spring Data REST • HATEOAS • Spring Cloud Contract • CDC • Pact
  • 4.
    © Tagbangers, Inc.3 背景 • SPA 時代 • よりリッチな UI 表現のために Thymeleaf から Angular などへ • ビジネスロジックの実装が UI 側にも必要なり… Browser Application • ビジネスロジック • CRUD Browser Application • ビジネスロジック • CRUD • ビジネスロジック DB DB
  • 5.
    © Tagbangers, Inc.4 工程やチーム構成の変化 BackendFrontend ex. Angular ex. Spring Boot Frontend Team Backend Team User repo repo CI/CDCI/CD code push code push artifactartifact 別々のデプロイ環境 別々のチーム 別々のレポジトリ 別々のリリースサイクル
  • 6.
    © Tagbangers, Inc.5 どの URI にアクセスすれば いいの? どんなフォーマットでデー タが取得できるの? 更新できる条件ってなんな の?? そこで発生してきた課題 開発初期段階
  • 7.
    © Tagbangers, Inc.6 API の仕様が変更されて UI がバグったんだけど〜 そこで発生してきた課題 リリース後のバージョンアップで
  • 8.
    © Tagbangers, Inc.7 解決策 どの URI にアクセスすればいいの? どんなフォーマットでデータが 取得できるの? 更新できる条件ってなんなの?? API の仕様が変更されて UI がバグったんだけど〜 Spring Data REST Spring エコシステムをフル活用! 標準化 + 自動生成 別々に進化できる仕組み Spring Cloud Contract
  • 9.
    Spring Data REST ✓標準化 + 自動生成
  • 10.
    © Tagbangers, Inc.9 残念な実装 class ReservationService { Reservation getReservation(long id) { return this.reservationRepository.findById(id); } Reservation createReservation(Reservation reservation) { return this.reservationRepository.save(reservation); } }
  • 11.
    © Tagbangers, Inc.10 Spring Data REST とは Entity GET /items GET /items/search GET / items/1 POST /items PUT /items/1 PATCH /items/1 DELETE /items/1 … Item ItemRepository REST スタイルな API エンドポイント URL 設計に迷わない Repository
  • 12.
    © Tagbangers, Inc.11 Spring Data REST の始め方 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
  • 13.
    © Tagbangers, Inc.12 Entity Repository @Entity public class Order { private Long id; private Location location; private LocalDateTime orderedDate; private Status status; } public interface OrderRepository extends PagingAndSortingRepository<Order, Long> { }
  • 14.
    © Tagbangers, Inc.13 . ____ _ __ _ _ / / ___'_ __ _ _(_)_ __ __ _ ( ( )___ | '_ | '_| | '_ / _` | / ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |___, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.6.RELEASE) ... Mapped "{[/ || ],methods=[HEAD],produces=[application/hal+json || application/json]}" ... Mapped "{[/ || ],methods=[GET],produces=[application/hal+json || application/json]}" ... Mapped "{[/ || ],methods=[OPTIONS],produces=[application/hal+json || application/json]}" ... Mapped "{[/{repository}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" ... Mapped "{[/{repository}],methods=[HEAD],produces=[application/hal+json || application/json]}" ... Mapped "{[/{repository}],methods=[GET],produces=[application/hal+json || application/json]}" ... Mapped "{[/{repository}],methods=[GET],produces=[application/x-spring-data-compact+json || text/uri-list]}" ... Mapped "{[/{repository}],methods=[POST],produces=[application/hal+json || application/json]}" ... Mapped "{[/{repository}/{id}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}],methods=[HEAD],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}],methods=[GET],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}],methods=[PUT],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}],methods=[PATCH],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}],methods=[DELETE],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}/{property}],methods=[GET],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}/{property}/{propertyId}],methods=[GET],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}/{property}],methods=[DELETE],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/{id}/{property}],methods=[GET],produces=[application/x-spring-data-compact+json || text/uri-list]}" Mapped "{[/{repository}/{id}/{property}],methods=[PATCH || PUT || POST],consumes=[application/json || application/x-spring-data-compact+json || text/uri-list], ... Mapped "{[/{repository}/{id}/{property}/{propertyId}],methods=[DELETE],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/search],methods=[OPTIONS],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/search],methods=[HEAD],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/search],methods=[GET],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/search/{search}],methods=[GET],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/search/{search}],methods=[GET],produces=[application/x-spring-data-compact+json]}" Mapped "{[/{repository}/search/{search}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" Mapped "{[/{repository}/search/{search}],methods=[HEAD],produces=[application/hal+json || application/json]}" Mapped "{[/profile],methods=[OPTIONS]}" Mapped "{[/profile],methods=[GET]}" Mapped "{[/profile/{repository}],methods=[GET],produces=[application/alps+json || */*]}" Mapped "{[/profile/{repository}],methods=[OPTIONS],produces=[application/alps+json]}" Mapped "{[/profile/{repository}],methods=[GET],produces=[application/schema+json]}" Mapped "{[/ || ],methods=[GET],produces=[text/html]}" Mapped "{[/browser],methods=[GET]}" 自動的にエンドポイントが登録されている /repository…
  • 15.
    © Tagbangers, Inc.14 { "firstName" : "Frodo", "lastName" : "Baggins", "_links" : { "self" : { "href" : "http://localhost:8080/people/1" } } } $ curl http://localhost:8080/people/1 LINK 部 DATA 部
  • 16.
    © Tagbangers, Inc.15 HATEOAS • Hypermedia as the Engine of Application State • データ部の他にリンク情報が追加され、関連するリソースをたどることができる • Spring Data REST のリクエスト・レスポンスボディ部のフォーマットは HATEOAS に準拠 • Spring HATEOAS は独立したライブラリでこれだけ使うこともできる
  • 17.
    © Tagbangers, Inc.16 フロント側で ID を取り出して個別に URL を組み立てる必要がない ページング情報も $ curl http://localhost:8080/people { "_links" : { "self" : { "href" : "http://localhost:8080/people{?page,size,sort}", "templated" : true }, "search" : { "href" : "http://localhost:8080/people/search" } }, "_embedded" : { "persons" : [ { "firstName" : "Frodo", "lastName" : "Baggins", "_links" : { "self" : { "href" : "http://localhost:8080/people/1" } } } ] }, "page" : { "size" : 20, "totalElements" : 1, "totalPages" : 1, "number" : 0 } } HATEOAS にのっかることでボディ部の フォーマットも悩まない
  • 18.
    © Tagbangers, Inc.17 { "description": "iPhone", "status": "IN_PROGRESS", "_links": { "self": { "href": "http://localhost:8080/orders/4" }, "orders": { "href": "http://localhost:8080/orders" }, "cancel": { "href": "http://localhost:8080/orders/4/cancel" }, "complete": { "href": "http://localhost:8080/orders/4/complete" } }
  • 19.
    if ( order.status== PENDING || order.status == PAID ) { showCancelButton } © Tagbangers, Inc. 18 ビジネスロジックの流出を最小限 に キャンセル判定 ロジック キャンセル判定 ロジック キャンセル判定 ロジック
  • 20.
    © Tagbangers, Inc.19 自動生成されて簡単!とはいえ… • とにかく簡単だけど… • Spring Data REST だけでは完成しない • 基本的に CRUD のみ • ビジネスロジックが必要な場合は拡張する Controller Service Repository Database ここは自分で書く Spring Data RESTが よしなに
  • 21.
    © Tagbangers, Inc.20 Repository Spring Data REST @RepositoryRest Controller Service @Controller @RestController Endpoint Endpoint Endpoint Domain Model ビジネスロジックの実装パターン EventHandler
  • 22.
    © Tagbangers, Inc.21 JSON Schema • JSON Schema から TypeScript のコードの自動生成 TypescriptJson schema 自動生成 curl -H 'Accept:application/schema+json' http://localhost:8080/profile/reservations { "title" : "Reservation", "properties" : { "date" : { "title" : "Date", "readOnly" : false, "type" : "string", "format" : "date-time" }, "name" : { "title" : "Name", "readOnly" : false, "type" : "string" } }, "definitions" : { }, "type" : "object", "$schema" : "http://json-schema.org/draft-04/schema#" } export type Date = string; export type Name = string; export interface Reservation { date?: Date; name?: Name; [k: string]: any; }
  • 23.
    © Tagbangers, Inc.22 Admin over Spring Data REST User UI Admin UI Spring Data REST Manual Impl DB
  • 24.
    © Tagbangers, Inc.23 全文検索 over Spring Data REST UI Elastic Search Spring Data REST Hibernate Search DB Custom Repository Impl
  • 25.
    © Tagbangers, Inc.24 マルチテナント over Spring Data REST UI Spring Data REST Repository DBHibernate Spring Security Authentication Event Listener Filter Tenant
  • 26.
    © Tagbangers, Inc.25 ポイント  Spring Data REST を活用してボイラープレートコードを削減する  REST や HATEOAS の流行りの仕様を活用する チーム間のコミュニケーションをフォーマット的なことではなく、 よりビジネス的にフォーカスしたいポイントに絞る!!
  • 27.
    © Tagbangers, Inc.26 ポイント BackendFrontend ex. Angular ex. Spring Boot Frontend Team Backend Team User repo repo CI/CDCI/CD code push code push artifactartifact Spring Data REST Spring HATEOAS
  • 28.
    Spring Cloud Contract でさくっと行う ConsumerDriven Contract with Pact 別々に進化できる仕組み
  • 29.
    © Tagbangers, Inc.28 工程やチーム構成の変化 BackendFrontend ex. Angular ex. Spring Boot Frontend Team Backend Team User repo repo CI/CDCI/CD code push code push artifactartifact 別々のデプロイ環境 別々のチーム 別々のレポジトリ 別々のリリースサイクル
  • 30.
    © Tagbangers, Inc.29 Test Pyramid https://martinfowler.com/bliki/TestPyramid.html
  • 31.
    © Tagbangers, Inc.30 モック に置き換える HTTP
  • 32.
    © Tagbangers, Inc.31 モック に置き換える その モック 意味がありますか? • インターフェースは正しい?? • 連携するサービスが変更されたら? • だれがメンテナンスするの?
  • 33.
    © Tagbangers, Inc.32 Micro service architecture Microservice MicroserviceMicroservice UI Microservice モックモックモックモックモックモックモックモック
  • 34.
    © Tagbangers, Inc.33 Spring Cloud Contract Spring Cloud Contract is an umbrella project holding solutions that help users in successfully implementing the Consumer Driven Contracts approach.
  • 35.
    © Tagbangers, Inc.34 Pact Pact is a contract testing tool. Contract testing is a way to ensure that services (such as an API provider and a client) can communicate with each other. Without contract testing, the only way to know that services can communicate is by using expensive and brittle integration tests
  • 36.
    © Tagbangers, Inc.35 Contract • 契約はインターフェース定義のようなもの • API 仕様書の内容 • Provider が契約を守ることを約束する Contract Consumer Consumer and provider are both agreed with the sentence below.. for example we agreed with on the basis of the Provider’s REST API, that is created hy Provider Test ✔ Test ✔
  • 37.
    Spring Cloud Contract+ Pact で Consumer Driven Contract
  • 38.
    © Tagbangers, Inc.37 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish Consumer 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) Provider https://github.com/pact-foundation/pact_broker/wiki/Webhooks
  • 39.
    © Tagbangers, Inc.38 1. テストを書く beforeAll ((done) => { provider = new PactWeb({ consumer: ‘sample-client-tenant', provider: ‘sapmle-server-tenant', port: 1234, host: '127.0.0.1' }); }); 前準備 1
  • 40.
    © Tagbangers, Inc.39 1. テストを書く beforeAll((done) => { provider.addInteraction({ uponReceiving: 'a request for hello', withRequest: { method: 'GET', path: '/hello' }, willRespondWith: { status: 200, headers: { 'Content-Type': 'application/hal+json;charset=UTF-8' }, body: { reply: 'Hello' } } }).then(done, done.fail); }); 前準備 2
  • 41.
    © Tagbangers, Inc.40 1. テストを書く it('should', (done) => { const greetingService: GreetingService = TestBed.get(GreetingTypeService); greetingService.hello().subscribe(response => { expect(response).toEqual({reply: 'Hello'}); done(); }, error => { done.fail(error); }); }); テスト /hello に Get リクエスト
  • 42.
    © Tagbangers, Inc.41 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) Consumer Provider
  • 43.
    © Tagbangers, Inc.42 2. Pact file ( Contract ) Pact file ... "interactions": [ { "description": "a request for hello", "request": { "method": "GET", "path": "/hello" }, "response": { "status": 200, "headers": { "Content-Type": "application/hal+json;charset=UTF-8" ...
  • 44.
    © Tagbangers, Inc.43 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) Contract を共有するためのアプリ https://github.com/pact-foundation/pact_broker/wiki/Webhooks Consumer Provider
  • 45.
    © Tagbangers, Inc.44 Pact Broker https://github.com/pact-foundation/pact_broker
  • 46.
    © Tagbangers, Inc.45 Pact Broker https://github.com/pact-foundation/pact_broker
  • 47.
    © Tagbangers, Inc.46 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) https://github.com/pact-foundation/pact_broker/wiki/Webhooks Consumer Provider
  • 48.
    © Tagbangers, Inc.47 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) ビルド時にテストを生成 + テスト実行 https://github.com/pact-foundation/pact_broker/wiki/Webhooks Consumer Provider
  • 49.
    © Tagbangers, Inc.48 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) https://github.com/pact-foundation/pact_broker/wiki/Webhooks Consumer Provider
  • 50.
    © Tagbangers, Inc.49 CDC ステップ 1. テスト書く 2. Contract(Pact file) を自動生成 3. Pact Broker に Pact file を publish 1. Pact Broker から Contract(Pact file) を取得 2. Test を自動生成(Spring Cloud Contract ) 3. モック を自動生成(Spring Cloud Contract) 成果物としてWireMockの定義ファイルができる https://github.com/pact-foundation/pact_broker/wiki/Webhooks Consumer Provider
  • 51.
    © Tagbangers, Inc.50 APIが実装されたか知りたい https://github.com/pact-foundation/pact_broker/wiki/Webhooks Broker のステータス確認で対応状況を把握
  • 52.
    © Tagbangers, Inc.51 Pact Broker https://github.com/pact-foundation/pact_broker
  • 53.
    © Tagbangers, Inc.52 破壊的な API の実装の変更 https://github.com/pact-foundation/pact_broker/wiki/Webhooks テストが通らないので破壊的な変更だとわかる!
  • 54.
    © Tagbangers, Inc.53 使用されている API がわかる! https://github.com/pact-foundation/pact_broker
  • 55.
  • 56.
    © Tagbangers, Inc.55 まとめ BackendFrontend ex. Angular ex. Spring Boot Frontend Team Backend Team User repo repo CI/CDCI/CD code push code push artifactartifact Spring Data REST Spring HATEOAS Spring Cloud Contract

Editor's Notes

  • #7 スタンダードな API 設計で・・・ Affordance
  • #21 Spring Data REST の話に戻して Spring Data REST のマッピングの優先順位は最低
  • #23 ビジネスロジックがフロントエンド側にも必要になり、モデルオブジェクトなど冗長なコードが発生していないでしょうか Spring Data REST はエンティティの情報から自動的に JSON Schema を作る機能もあります。 この JSON Schema の情報を使って例えば Angular でしたら Typescript の型をコード生成することも可能です。
  • #29 Spring Cloud Contract Consumer Driven Contract Contract Testing
  • #30 チームを分割したことにより ・ お互いのチームの開発進捗に関係なく開発を進めたい ・ 独自に進化していきたい 変更を加えても壊れないシステムにしたいですよね。 壊れてしまうのであればデプロイ前に知ることができると安心して開発を進めることが出来ます。 アプリケーションをバージョンアップし、自身をもってデプロイしたい
  • #31 テストの自動化にも課題はたくさんあります。 テストピラミッドが表すように、e2e、integration test と呼ばれるようなテストは ・コストがかかる ・時間がかかる →エラーの検知に時間がかかる ・環境構築が複雑 → 環境起因のエラーに悩まされる せっかくチームを分割して専門性を高め、独自に進化していきたいのにテストに足を引っ張られてしまっては意味がありません。 e2e、 integration test を代替する方法を考えなければいけません。 もっとも簡単な方法は integration する相手を モック に置き換えてしまうことです。 モック、スタブ、テストダブルなど言葉はいろいろとありますが、今日はモックという言葉で統一させてもらう
  • #32 モック に置き換えると ・テストのための環境構築がらく ・速い というメリットがありますが
  • #33 モック に置き換えると ・テストのための環境構築がらく ・速い というメリットがありますが インターフェースは正しい?? 連携するサービスが変更されたら? だれがメンテナンスするの? メリットも大きいですが、さらなる問題点が出てきてしまいます。 意味のある モック を維持するのはとても大変です
  • #34 UI と API を分けただけなのに、はやりの Architecture はもっと大変なは・・・・
  • #35 ありますよ。 Spring Cloud Contract。 Spring Cloud とついているので一見 UI + API くらいの分割では意味のないものに思えますが、 ちゃんと使えます。 Consumer Driven Contract の実装を手助け Spring Cloud Contract はとても雑にいうと 意味のある モック を簡単に準備する便利ツールです
  • #36 ありますよ。 Spring Cloud Contract。 Spring Cloud とついているので一見 UI + API くらいの分割では意味のないものに思えますが、 ちゃんと使えます。 Spring Cloud Contract はとても雑にいうと 意味のある モック を簡単に準備する便利ツールです
  • #37 契約というのは現実世界の契約とそれほど違いはなくて、 例えば携帯電話の契約みたいなもんです。 契約書の内容を確認して問題なければ、サインしますよね。 サインしたら契約完了で、端末だったり通信サービスを受けることが出来ます。 Spring Cloud Contract、 CDC の Contract も同じようなもので 契約の内容をそれぞれのアプリケーションが確認して、間違いなければサインする すると、mock や e2e を補完してくれような恩恵を受けることができるということです。
  • #39 ここからは Consumer と Provider という言葉を使います
  • #41 期待するレスポンス
  • #42 期待するレスポンス
  • #44 期待するレスポンス
  • #46 Pact ファイル と呼ばれる Contract を共有するための web application Docker などで自分で用意することもできるし Hostされているフリーの Broker service もあるよ 自前で準備するのも面倒なのであるプロジェクトではフリーの Broker を使ったりもしています
  • #47 今回の例は 1 対 1 の 連携ですが Micro service のように多数のサービスが存在するばいいに便利な Service の連携を可視化する機能もあります HAL Browser もあるよ API ドキュメントも参照できる かなり便利なアプリケーション
  • #50 Consumer 側では テストを検証することで アプリケーションが実際にAPIを使用していることを Provider 側では 自動生成Contract が守られる
  • #51 モックを自動生成とかいておりますが、実際には Spring Cloud Contract が対応している WireMock という モックサーバー の定義ファイルが出来ます。 WireMock は 定義ファイルを元に動作するモックサーバーです
  • #53 Pact ファイルを共有するための web application Docker などで自分で用意することもできるし HostされているフリーのBroker もあるよ
  • #55 Pact ファイルを共有するための web application Docker などで自分で用意することもできるし HostされているフリーのBroker もあるよ
  • #57 Spring Data REST でをフル活用することで「いつもの」(単純なCRUDとか)作業は自動生成! HATEOAS を採用してらくに設計をしよう! → フォーカスしたいポイントに集中しよう! コストの高い Integration Test を CDC に置き換えて有効なテストを! →別々に進化できる仕組み