Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

1,568 views

Published on

Spring Data REST
Spring HATEOAS
Spring Cloud Contract
CDC
Pact

Published in: Technology
  • Be the first to comment

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

  1. 1. Spring Data REST と Spring Cloud Contract Tagbangers, Inc. #sf_25 #アンケート
  2. 2. © Tagbangers, Inc. 1 自己紹介 小川岳史 Spring Lover 10 years 山﨑大 わさもん
  3. 3. © Tagbangers, Inc. 2 本日のメニュー • 背景 • Spring Data REST • HATEOAS • Spring Cloud Contract • CDC • Pact
  4. 4. © Tagbangers, Inc. 3 背景 • SPA 時代 • よりリッチな UI 表現のために Thymeleaf から Angular などへ • ビジネスロジックの実装が UI 側にも必要なり… Browser Application • ビジネスロジック • CRUD Browser Application • ビジネスロジック • CRUD • ビジネスロジック DB DB
  5. 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. 6. © Tagbangers, Inc. 5 どの URI にアクセスすれば いいの? どんなフォーマットでデー タが取得できるの? 更新できる条件ってなんな の?? そこで発生してきた課題 開発初期段階
  7. 7. © Tagbangers, Inc. 6 API の仕様が変更されて UI がバグったんだけど〜 そこで発生してきた課題 リリース後のバージョンアップで
  8. 8. © Tagbangers, Inc. 7 解決策 どの URI にアクセスすればいいの? どんなフォーマットでデータが 取得できるの? 更新できる条件ってなんなの?? API の仕様が変更されて UI がバグったんだけど〜 Spring Data REST Spring エコシステムをフル活用! 標準化 + 自動生成 別々に進化できる仕組み Spring Cloud Contract
  9. 9. Spring Data REST ✓ 標準化 + 自動生成
  10. 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. 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. 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. 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. 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. 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. 16. © Tagbangers, Inc. 15 HATEOAS • Hypermedia as the Engine of Application State • データ部の他にリンク情報が追加され、関連するリソースをたどることができる • Spring Data REST のリクエスト・レスポンスボディ部のフォーマットは HATEOAS に準拠 • Spring HATEOAS は独立したライブラリでこれだけ使うこともできる
  17. 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. 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. 19. if ( order.status == PENDING || order.status == PAID ) { showCancelButton } © Tagbangers, Inc. 18 ビジネスロジックの流出を最小限 に キャンセル判定 ロジック キャンセル判定 ロジック キャンセル判定 ロジック
  20. 20. © Tagbangers, Inc. 19 自動生成されて簡単!とはいえ… • とにかく簡単だけど… • Spring Data REST だけでは完成しない • 基本的に CRUD のみ • ビジネスロジックが必要な場合は拡張する Controller Service Repository Database ここは自分で書く Spring Data RESTが よしなに
  21. 21. © Tagbangers, Inc. 20 Repository Spring Data REST @RepositoryRest Controller Service @Controller @RestController Endpoint Endpoint Endpoint Domain Model ビジネスロジックの実装パターン EventHandler
  22. 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. 23. © Tagbangers, Inc. 22 Admin over Spring Data REST User UI Admin UI Spring Data REST Manual Impl DB
  24. 24. © Tagbangers, Inc. 23 全文検索 over Spring Data REST UI Elastic Search Spring Data REST Hibernate Search DB Custom Repository Impl
  25. 25. © Tagbangers, Inc. 24 マルチテナント over Spring Data REST UI Spring Data REST Repository DBHibernate Spring Security Authentication Event Listener Filter Tenant
  26. 26. © Tagbangers, Inc. 25 ポイント  Spring Data REST を活用してボイラープレートコードを削減する  REST や HATEOAS の流行りの仕様を活用する チーム間のコミュニケーションをフォーマット的なことではなく、 よりビジネス的にフォーカスしたいポイントに絞る!!
  27. 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. 28. Spring Cloud Contract でさくっと行う Consumer Driven Contract with Pact 別々に進化できる仕組み
  29. 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. 30. © Tagbangers, Inc. 29 Test Pyramid https://martinfowler.com/bliki/TestPyramid.html
  31. 31. © Tagbangers, Inc. 30 モック に置き換える HTTP
  32. 32. © Tagbangers, Inc. 31 モック に置き換える その モック 意味がありますか? • インターフェースは正しい?? • 連携するサービスが変更されたら? • だれがメンテナンスするの?
  33. 33. © Tagbangers, Inc. 32 Micro service architecture Microservice MicroserviceMicroservice UI Microservice モックモックモックモックモックモックモックモック
  34. 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. 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. 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. 37. Spring Cloud Contract + Pact で Consumer Driven Contract
  38. 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. 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. 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. 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. 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. 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. 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. 45. © Tagbangers, Inc. 44 Pact Broker https://github.com/pact-foundation/pact_broker
  46. 46. © Tagbangers, Inc. 45 Pact Broker https://github.com/pact-foundation/pact_broker
  47. 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. 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. 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. 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. 51. © Tagbangers, Inc. 50 APIが実装されたか知りたい https://github.com/pact-foundation/pact_broker/wiki/Webhooks Broker のステータス確認で対応状況を把握
  52. 52. © Tagbangers, Inc. 51 Pact Broker https://github.com/pact-foundation/pact_broker
  53. 53. © Tagbangers, Inc. 52 破壊的な API の実装の変更 https://github.com/pact-foundation/pact_broker/wiki/Webhooks テストが通らないので破壊的な変更だとわかる!
  54. 54. © Tagbangers, Inc. 53 使用されている API がわかる! https://github.com/pact-foundation/pact_broker
  55. 55. まとめ
  56. 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

×