Save 37% off PRO during our Black Friday Sale! »

マイクロサービスアーキテクチャをあきらめないための、モノリスで始めるアーキテクチャテスト / #jjug_ccc_b #ccc_b8 / JJUG CCC 2020 Fall

73560128b23de542e47a318145bc781a?s=47 Yu Kawanami
November 07, 2020

マイクロサービスアーキテクチャをあきらめないための、モノリスで始めるアーキテクチャテスト / #jjug_ccc_b #ccc_b8 / JJUG CCC 2020 Fall

73560128b23de542e47a318145bc781a?s=128

Yu Kawanami

November 07, 2020
Tweet

Transcript

  1. Microservices architecture を あきらめないための、 Monolith で始める アーキテクチャテスト JJUG CCC 2020

    Fall @kawanamiyuu
  2. Architecture testing to evolve Monolithic architecture into the Microservices architecture.

    JJUG CCC 2020 Fall @kawanamiyuu
  3. 自己紹介 • かわなみゆう • @kawanamiyuu • 株式会社ラクス / Lead Engineer

    • 人事・労務業務を楽にする SaaS の開発 • Spring Boot / Doma / Vue.js / Puppeteer / GitLab CI • まさかアーキテクチャテストでネタかぶりするとは! 3
  4. 今日話すこと 1. 新規プロダクト立ち上げ時の、モノリスという選択 2. モノリスあるあると、アーキテクチャ設計という営みの課題 3. アーキテクチャテストの紹介 4. モノリスの発展性を支えるアーキテクチャテスト 4

  5. 新規プロダクト立ち上げ時の、 モノリスという選択 5 Photo by Dan Meyers on Unsplash

  6. さあ始めよう!MSA ? モノリス ? 新しくプロダクトを始めようというときに、クラウドとか MSA とか流 行りの開発言語とか、いろいろ技術的な夢をみがち 6

  7. 新規プロダクト立ち上げ時に(技術的に)やること 1. 対象の業務領域(ドメイン)・業務要求の理解、分析 2. 満たすべき品質特性の特定、優先度付け 3. 技術スタックの選定 ◦ アーキテクチャ ◦

    ミドルウェア ◦ 開発言語 ◦ etc 7
  8. アーキテクチャの選択肢 • マイクロサービスアーキテクチャ • モノリシックアーキテクチャ 8

  9. 「新規プロダクト立ち上げ」時のアーキテク チャ選択における課題感 9

  10. アーキテクチャ選択における課題感 • 業務ドメインに対する知識の少なさから、適切な粒度のサービ ス分割が難しい • そもそも売れるか分からない、仮説検証を高速にまわしてい かなければいけない段階で、MSA で開発・運用するオーバー ヘッドが大きい •

    手段が目的化している感 10
  11. 11 https://tech-blog.rakus.co.jp/entry/20180926/microservice

  12. アーキテクチャ選択における課題感 • 業務ドメインに対する知識の少なさから、適切な粒度のサービ ス分割が難しい • そもそも売れるか分からない、仮説検証を高速にまわしてい かなければいけない段階で、MSA で開発・運用するオーバー ヘッドが大きい •

    手段が目的化している感 12
  13. アーキテクチャ選択における課題感 • 業務ドメインに対する知識の少なさから、適切な粒度のサービ ス分割が難しい • そもそも売れるか分からない、仮説検証を高速にまわしてい かなければいけない段階で、MSA で開発・運用するオーバー ヘッドが大きい •

    手段が目的化している感 13
  14. アーキテクチャ選択における課題感 • 業務ドメインに対する知識の少なさから、適切な粒度のサービ ス分割が難しい • そもそも売れるか分からない、仮説検証を高速にまわしてい かなければいけない段階で、MSA で開発・運用するオーバー ヘッドが大きい •

    手段が目的化している感 14
  15. アーキテクチャ選択における課題感 • 業務ドメインに対する知識の少なさから、適切な粒度のサービ ス分割が難しい • そもそも売れるか分からない、仮説検証を高速にまわしてい かなければいけない段階で、MSA で開発・運用するオーバー ヘッドが大きい •

    手段が目的化している感 15 このような課題感から現実的な解としての モノリシックなアプリケーション開発
  16. モノリスあるある、と アーキテクチャ設計という営みの課題 16 Photo by 贝莉儿 DANIST on Unsplash

  17. モノリスあるある 17

  18. モノリスあるある • 適切なモジュール分割を実現するためにドメイン駆動設計や、 具体的な設計パターンとしてクリーンアーキテクチャやレイ ヤードアーキテクチャなどの方法をとることが多い • あとから分割すればよいと開発を始めたモノリスで、いざ分割 を検討する段階でアプリケーション内の依存関係が複雑に絡 み合い、分割したくとも解きほぐすのが困難 18

  19. モノリスあるある • 適切なモジュール分割を実現するためにドメイン駆動設計や、 具体的な設計パターンとしてクリーンアーキテクチャやレイ ヤードアーキテクチャなどの方法をとることが多い • あとから分割すればよいと開発を始めたモノリスで、いざ分割 を検討する段階でアプリケーション内の依存関係が複雑に絡 み合い、分割したくとも解きほぐすのが困難 19

  20. アーキテクチャ設計という営みの課題 20

  21. こんな悩みありませんか? 21

  22. アーキテクトの悩み • 開発初期に頑張って検討した設計方針が、納期優先・相次ぐ メンバー増員により、いつのまにか泥団子に • 開発プロセスとしてコードレビューは機能しているが、アーキテ クチャの観点ではレビューされない • 開発メンバーに設計力を上げてもらうためにチャレンジさせた いけど、丸投げするのはちょっと不安

    22
  23. アーキテクトの悩み • 開発初期に頑張って検討した設計方針が、納期優先・相次ぐ メンバー増員により、いつのまにか泥団子に • 開発プロセスとしてコードレビューは機能しているが、アーキテ クチャの観点ではレビューされない • 開発メンバーに設計力を上げてもらうためにチャレンジさせた いけど、丸投げするのはちょっと不安

    23
  24. アーキテクトの悩み • 開発初期に頑張って検討した設計方針が、納期優先・相次ぐ メンバー増員により、いつのまにか泥団子に • 開発プロセスとしてコードレビューは機能しているが、アーキテ クチャの観点ではレビューされない • 開発メンバーに設計力を上げてもらうためにチャレンジさせた いけど、丸投げするのはちょっと不安

    24
  25. アーキテクトの悩み • 開発初期に頑張って検討した設計方針が、納期優先・相次ぐ メンバー増員により、いつのまにか泥団子に • 開発プロセスとしてコードレビューは機能しているが、アーキテ クチャの観点ではレビューされない • 開発メンバーに設計力を上げてもらうためにチャレンジさせた いけど、丸投げするのはちょっと不安

    25
  26. 開発メンバーの悩み • どのパッケージにクラスを置いたらいいか毎回迷う • コードレビューで指摘されたけど、そんなルール聞いていな し、ドキュメントもないので知りようがない • ドメイン駆動設計?Clean Architecture?難しそうだし、ソー スコードがどうあればそれらが適用されたアーキテクチャとい

    えるのかイメージできない 26
  27. 開発メンバーの悩み • どのパッケージにクラスを置いたらいいか毎回迷う • コードレビューで指摘されたけど、そんなルール聞いていな し、ドキュメントもないので知りようがない • ドメイン駆動設計?Clean Architecture?難しそうだし、ソー スコードがどうあればそれらが適用されたアーキテクチャとい

    えるのかイメージできない 27
  28. 開発メンバーの悩み • どのパッケージにクラスを置いたらいいか毎回迷う • コードレビューで指摘されたけど、そんなルール聞いていな し、ドキュメントもないので知りようがない • ドメイン駆動設計?Clean Architecture?難しそうだし、ソー スコードがどうあればそれらが適用されたアーキテクチャとい

    えるのかイメージできない 28
  29. 開発メンバーの悩み • どのパッケージにクラスを置いたらいいか毎回迷う • コードレビューで指摘されたけど、そんなルール聞いていな し、ドキュメントもないので知りようがない • ドメイン駆動設計?Clean Architecture?難しそうだし、ソー スコードがどうあればそれらが適用されたアーキテクチャとい

    えるのかイメージできない 29
  30. 悩みの原因 • アーキテクチャ設計に関する知識が属人化している • アーキテクチャ設計に関する知識が暗黙知化している • 知っている人が人力でチェックするしかない • 知らなければ当然、チェックされることなくすり抜けてしまう 30

  31. 悩みの原因 • アーキテクチャ設計に関する知識が属人化している • アーキテクチャ設計に関する知識が暗黙知化している • 知っている人が人力でチェックするしかない • 知らなければ当然、チェックされることなくすり抜けてしまう 31

    ソースコードの品質担保以上に、アーキテクチャの品質担保は難しい
  32. アーキテクチャの正体 32

  33. Context Map 33 https://www.infoq.com/articles/ddd-contextmapping/

  34. Context Map 34 https://www.infoq.com/articles/ddd-contextmapping/

  35. Layered Architecture (〇〇〇〇を逆転したレイヤードアーキテクチャ) 35 (一般的なレイヤードアーキテクチャ)

  36. Layered Architecture (〇〇〇〇を逆転したレイヤードアーキテクチャ) 36 (一般的なレイヤードアーキテクチャ)

  37. Clean Architecture 37

  38. Clean Architecture 38

  39. Domain Model 39

  40. 依存関係 40

  41. アーキテクチャとは 「依存関係」のガイドライン 41

  42. アーキテクチャの正体 • アーキテクチャとはソフトウェアの構造についての取り決めで あり、 • 解像度を上げていくと、ソフトウェアを構成する責務や関心事 の「依存関係」についての取り決め • (乱暴に言うと)Layered Architecture

    も Clean Architecture も 、DDD のような設計論も、依存関係を適切 に設計したいだけ 42
  43. 「アーキテクチャ」の問題点 43

  44. 「ガイドライン」というものの性質 • ガイドラインとは「指針」「ルール」「マナー」 • 人が決めて、守る(守らせる) • 最初にルールをつくるのは、簡単 • ルール通りつくり始めるのも、簡単 44

  45. 「ガイドライン」というものの性質 • ガイドラインとは「指針」「ルール」「マナー」 • 人が決めて、守る(守らせる) • 最初にルールをつくるのは、簡単 • ルール通りつくり始めるのも、簡単  なにが難しいのか?

    45
  46. 「ガイドライン」というものの性質 • ガイドラインとは「指針」「ルール」「マナー」 • 人が決めて、守る(守らせる) • 最初にルールをつくるのは、簡単 • ルール通りつくり始めるのも、簡単  なにが難しいのか?

    46 アーキテクチャの維持が難しい。 人が決めたものであるがゆえ、壊れやすい。
  47. どうすればよいか? 47

  48. 48 アーキテクチャをテストしたい

  49. アーキテクチャテストの紹介 49 Photo by ThisisEngineering RAEng on Unsplash

  50. 50 https://www.archunit.org/

  51. ArchUnit • GitHub ◦ https://github.com/TNG/ArchUnit ◦ https://github.com/TNG/ArchUnit-Examples • Twitter ◦

    https://twitter.com/archtests • Technology Radar (2018) ◦ https://www.thoughtworks.com/radar/tools/archunit ◦ 進化的アーキテクチャ x 適応度関数 51
  52. ArchUnit を一言でいうと • Java(や Kotlin, Scala)で書かれたアプリケーションのパッ ケージやクラスの依存関係を JUnit のテストコードとして表現 し、テストできるテストフレームワーク

    • 自動化により、一定の強制力をもってアーキテクチャの設計品 質を担保し続けることができる • 依存関係の他にも、そのアプリケーション固有の実装ルール をコード化して、テストすることもできる 52
  53. ArchUnit を一言でいうと • Java(や Kotlin, Scala)で書かれたアプリケーションのパッ ケージやクラスの依存関係を JUnit のテストコードとして表現 し、テストできるテストフレームワーク

    • 自動化により、一定の強制力をもってアーキテクチャの設計品 質を担保し続けることができる • 依存関係の他にも、そのアプリケーション固有の実装ルール をコード化して、テストすることもできる 53
  54. ArchUnit を一言でいうと • Java(や Kotlin, Scala)で書かれたアプリケーションのパッ ケージやクラスの依存関係を JUnit のテストコードとして表現 し、テストできるテストフレームワーク

    • 自動化により、一定の強制力をもってアーキテクチャの設計品 質を担保し続けることができる • 依存関係の他にも、そのアプリケーション固有の実装ルール をコード化して、テストすることもできる 54
  55. 他のプログラミング言語でのアーキテクチャテスト • TNG/ArchUnitNET(C#) • iternity/archlint.cs(C#) • BenMorris/NetArchTest(.Net) • sensiolabs-de/deptrac(PHP) •

    carlosas/phpat(PHP) • nazonohito51/dependency-analyzer(PHP) 55
  56. アーキテクチャの典型的なテストの例 56

  57. アーキテクチャの典型的なテスト • レイヤードアーキテクチャ • パッケージ間・クラス間の依存管理 • 実行環境・フレームワークへの依存管理 57

  58. アーキテクチャの典型的なテスト • レイヤードアーキテクチャ • パッケージ間・クラス間の依存管理 • 実行環境・フレームワークへの依存管理 58

  59. 59 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  60. 60 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  61. 61 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  62. 62 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  63. 63 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  64. 64 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  65. 65 @Test void DIP_依存性逆転の原則_を適用したレイヤードアーキテクチャ () { layeredArchitecture() .layer("ui").definedBy("com.example.presentation..") .layer("app").definedBy("com.example.application..") .layer("domain").definedBy("com.example.domain..")

    .layer("infra").definedBy("com.example.infrastructure..") .whereLayer("ui").mayOnlyBeAccessedByLayers("infra") .whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui") .whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app") .whereLayer("infra").mayNotBeAccessedByAnyLayer() .check(CLASSES); }
  66. 66 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ () FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may only be accessed by layers ['infra'] where layer 'app' may only be accessed by layers ['infra', 'ui'] where layer 'domain' may only be accessed by layers ['infra', 'app'] where layer 'infra' may not be accessed by any layer was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  67. 67 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ () FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may only be accessed by layers ['infra'] where layer 'app' may only be accessed by layers ['infra', 'ui'] where layer 'domain' may only be accessed by layers ['infra', 'app'] where layer 'infra' may not be accessed by any layer was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  68. 68 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ () FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may only be accessed by layers ['infra'] where layer 'app' may only be accessed by layers ['infra', 'ui'] where layer 'domain' may only be accessed by layers ['infra', 'app'] where layer 'infra' may not be accessed by any layer was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  69. 69 com.example.ArchitectureTest > DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM]

    - Rule Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may only be accessed by layers ['infra'] where layer 'app' may only be accessed by layers ['infra', 'ui'] where layer 'domain' may only be accessed by layers ['infra', 'app'] where layer 'infra' may not be accessed by any layer was violated (2 times):
  70. 70 com.example.ArchitectureTest > DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM]

    - Rule Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may only be accessed by layers ['infra'] where layer 'app' may only be accessed by layers ['infra', 'ui'] where layer 'domain' may only be accessed by layers ['infra', 'app'] where layer 'infra' may not be accessed by any layer was violated (2 times):
  71. 71 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ () FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may only be accessed by layers ['infra'] where layer 'app' may only be accessed by layers ['infra', 'ui'] where layer 'domain' may only be accessed by layers ['infra', 'app'] where layer 'infra' may not be accessed by any layer was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  72. 72 Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in

    (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  73. 73 Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in

    (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  74. 74 Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in

    (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  75. 75 Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in

    (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:178) at com.example.ArchitectureTest.DIP_依存性逆転の原則_を適用したレイヤーアーキテクチャ (ArchitectureTest.java:28)
  76. アーキテクチャの典型的なテスト • レイヤードアーキテクチャ • パッケージ間・クラス間の依存管理 • 実行環境・フレームワークへの依存管理 76

  77. 77 @Test void UI層のクラスはインフラストラクチャ層のクラスからのみ依存される() { classes().that().resideInAPackage("com.example.presentation..") .should() .onlyHaveDependentClassesThat().resideInAPackage("com.example.infrastructure..") .check(CLASSES); }

    @Test void ドメイン層のクラスは他の層のクラスに依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAnyPackage( "com.example.presentation..", "com.example.application..", "com.example.infrastructure..") .check(CLASSES); }
  78. 78 @Test void UI層のクラスはインフラストラクチャ層のクラスからのみ依存される() { classes().that().resideInAPackage("com.example.presentation..") .should() .onlyHaveDependentClassesThat().resideInAPackage("com.example.infrastructure..") .check(CLASSES); }

    @Test void ドメイン層のクラスは他の層のクラスに依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAnyPackage( "com.example.presentation..", "com.example.application..", "com.example.infrastructure..") .check(CLASSES); }
  79. 79 @Test void UI層のクラスはインフラストラクチャ層のクラスからのみ依存される() { classes().that().resideInAPackage("com.example.presentation..") .should() .onlyHaveDependentClassesThat().resideInAPackage("com.example.infrastructure..") .check(CLASSES); }

    @Test void ドメイン層のクラスは他の層のクラスに依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAnyPackage( "com.example.presentation..", "com.example.application..", "com.example.infrastructure..") .check(CLASSES); }
  80. 80 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > ドメイン層のクラスは他の層のクラスに依存しない() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package 'com.example.domain..' should depend on classes that reside in any package ['com.example.presentation..', 'com.example.application..', 'com.example.infrastructure..']' was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.lang.ArchRule$Factory$SimpleArchRule.check(ArchRule.java:195) at com.tngtech.archunit.lang.syntax.ObjectsShouldInternal.check(ObjectsShouldInternal.java:81) at com.example.ArchitectureTest.ドメイン層のクラスは他の層のクラスに依存しない(ArchitectureTest.java:38)
  81. 81 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > ドメイン層のクラスは他の層のクラスに依存しない() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package 'com.example.domain..' should depend on classes that reside in any package ['com.example.presentation..', 'com.example.application..', 'com.example.infrastructure..']' was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.lang.ArchRule$Factory$SimpleArchRule.check(ArchRule.java:195) at com.tngtech.archunit.lang.syntax.ObjectsShouldInternal.check(ObjectsShouldInternal.java:81) at com.example.ArchitectureTest.ドメイン層のクラスは他の層のクラスに依存しない(ArchitectureTest.java:38)
  82. 82 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > ドメイン層のクラスは他の層のクラスに依存しない() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package 'com.example.domain..' should depend on classes that reside in any package ['com.example.presentation..', 'com.example.application..', 'com.example.infrastructure..']' was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.lang.ArchRule$Factory$SimpleArchRule.check(ArchRule.java:195) at com.tngtech.archunit.lang.syntax.ObjectsShouldInternal.check(ObjectsShouldInternal.java:81) at com.example.ArchitectureTest.ドメイン層のクラスは他の層のクラスに依存しない(ArchitectureTest.java:38)
  83. 83 $ ./gradlew clean test > Task :test FAILED com.example.ArchitectureTest

    > ドメイン層のクラスは他の層のクラスに依存しない() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package 'com.example.domain..' should depend on classes that reside in any package ['com.example.presentation..', 'com.example.application..', 'com.example.infrastructure..']' was violated (2 times): Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) Field <com.example.domain.employee.EmployeeService.repository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:91) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81) at com.tngtech.archunit.lang.ArchRule$Factory$SimpleArchRule.check(ArchRule.java:195) at com.tngtech.archunit.lang.syntax.ObjectsShouldInternal.check(ObjectsShouldInternal.java:81) at com.example.ArchitectureTest.ドメイン層のクラスは他の層のクラスに依存しない(ArchitectureTest.java:38)
  84. アーキテクチャの典型的なテスト • レイヤードアーキテクチャ • パッケージ間・クラス間の依存管理 • 実行環境・フレームワークへの依存管理 84

  85. 85 @Test void ドメイン層はWeb実行環境に依存しない () { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAPackage("javax.servlet..") .check(CLASSES);

    } @Test void ドメイン層はWebアプリケーションフレームワークに依存しない () { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAPackage("org.springframework..") .check(CLASSES); }
  86. 86 @Test void ドメイン層はWeb実行環境に依存しない () { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAPackage("javax.servlet..") .check(CLASSES);

    } @Test void ドメイン層はWebアプリケーションフレームワークに依存しない () { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAPackage("org.springframework..") .check(CLASSES); }
  87. モノリスの発展性を支える アーキテクチャテスト 87 Photo by Markus Spiske on Unsplash

  88. アーキテクチャの視座 • モノリス内に存在する責務や関心事を適切な粒度で切り分け る ◦ 大きな粒度:レイヤー、ドメイン ◦ 小さな粒度:パッケージ、クラス • それぞれの粒度で依存関係を注意深く設計する

    88
  89. アーキテクチャの視座 • モノリス内に存在する責務や関心事を適切な粒度で切り分け る ◦ 大きな粒度:レイヤー、ドメイン ◦ 小さな粒度:パッケージ、クラス • それぞれの粒度で依存関係を注意深く設計する

    ◦ モノリスを将来、マイクロサービスに分割するという観点では、ドメイン 間の依存関係(=実体としてはパッケージ間の依存関係)にはとくに注 目 89
  90. 依存関係を守り、育てる • 「守る」手段としてのアーキテクチャテスト • 「育てる」手段としてのアーキテクチャテスト 90

  91. 「守る」手段としてのアーキテクチャテスト • Layered Architecture のアーキテクチャテスト • Onion Architecture のアーキテクチャテスト •

    (Clean Architecture のアーキテクチャテスト) • 実行環境やフレームワークへの依存管理 • ドメイン間(パッケージ間)の依存管理 91
  92. 「育てる」手段としてのアーキテクチャテスト? 92

  93. アーキテクチャテストの失敗は悪か? • アーキテクチャテストはあるべき依存関係を表現したもの • アーキテクチャテストは失敗しないはずのテスト • アーキテクチャテストが失敗するとき ◦ 実装誤り ◦

      ◦ 93
  94. アーキテクチャテストの失敗は悪か? • アーキテクチャテストはあるべき依存関係を表現したもの • アーキテクチャテストは失敗しないはずのテスト • アーキテクチャテストが失敗するとき ◦ 実装誤り ◦

    アーキテクチャに対する発見のサイン ◦ アーキテクチャについて議論する始点 94
  95. 「育てる」手段としてのアーキテクチャテスト • プロダクト立ち上げ時に、MSA の適切なサービス分割がわか らないのと同じく、モノリスにおけるモジュール分割も最初から うまくいくわけではない • ソフトウェアの成長の過程で、アーキテクチャに対してもフィー ドバックを得て、適切な依存関係を発見していく必要がある 95

  96. 「育てる」手段としてのアーキテクチャテスト • プロダクト立ち上げ時に、MSA の適切なサービス分割がわか らないのと同じく、モノリスにおけるモジュール分割も最初から うまくいくわけではない • ソフトウェアの成長の過程で、アーキテクチャに対してもフィー ドバックを得て、適切な依存関係を発見していく必要がある 96

  97. 最後に 97

  98. アーキテクチャの目的 98

  99. 99 https://www.infoq.com/articles/ddd-contextmapping/

  100. この図は • モノリシックなアプリケーション内部の、ドメイン同士の依存関 係? • マイクロサービスアーキテクチャで実現されたアプリケーション の、サービス同士の依存関係? 100

  101. この図は • モノリシックなアプリケーション内部の、ドメイン同士の依存関 係? • マイクロサービスアーキテクチャで実現されたアプリケーション の、サービス同士の依存関係? どちらもありえる。 101

  102. ソフトウェアが「ソフト」であるための選択肢を残す。 それがアーキテクチャ。 そのアーキテクチャが意図する依存関係を維持し、ソフトウェアの 発展の可能性を支えるアーキテクチャテスト。 102