Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Architecture testing to evolve Monolithic architecture into the Microservices architecture. JJUG CCC 2020 Fall @kawanamiyuu

Slide 3

Slide 3 text

自己紹介 ● かわなみゆう ● @kawanamiyuu ● 株式会社ラクス / Lead Engineer ● 人事・労務業務を楽にする SaaS の開発 ● Spring Boot / Doma / Vue.js / Puppeteer / GitLab CI ● まさかアーキテクチャテストでネタかぶりするとは! 3

Slide 4

Slide 4 text

今日話すこと 1. 新規プロダクト立ち上げ時の、モノリスという選択 2. モノリスあるあると、アーキテクチャ設計という営みの課題 3. アーキテクチャテストの紹介 4. モノリスの発展性を支えるアーキテクチャテスト 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

アーキテクチャの選択肢 ● マイクロサービスアーキテクチャ ● モノリシックアーキテクチャ 8

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

11 https://tech-blog.rakus.co.jp/entry/20180926/microservice

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

モノリスあるある、と アーキテクチャ設計という営みの課題 16 Photo by 贝莉儿 DANIST on Unsplash

Slide 17

Slide 17 text

モノリスあるある 17

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

悩みの原因 ● アーキテクチャ設計に関する知識が属人化している ● アーキテクチャ設計に関する知識が暗黙知化している ● 知っている人が人力でチェックするしかない ● 知らなければ当然、チェックされることなくすり抜けてしまう 31 ソースコードの品質担保以上に、アーキテクチャの品質担保は難しい

Slide 32

Slide 32 text

アーキテクチャの正体 32

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Clean Architecture 37

Slide 38

Slide 38 text

Clean Architecture 38

Slide 39

Slide 39 text

Domain Model 39

Slide 40

Slide 40 text

依存関係 40

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

アーキテクチャの正体 ● アーキテクチャとはソフトウェアの構造についての取り決めで あり、 ● 解像度を上げていくと、ソフトウェアを構成する責務や関心事 の「依存関係」についての取り決め ● (乱暴に言うと)Layered Architecture も Clean Architecture も 、DDD のような設計論も、依存関係を適切 に設計したいだけ 42

Slide 43

Slide 43 text

「アーキテクチャ」の問題点 43

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

「ガイドライン」というものの性質 ● ガイドラインとは「指針」「ルール」「マナー」 ● 人が決めて、守る(守らせる) ● 最初にルールをつくるのは、簡単 ● ルール通りつくり始めるのも、簡単  なにが難しいのか? 46 アーキテクチャの維持が難しい。 人が決めたものであるがゆえ、壊れやすい。

Slide 47

Slide 47 text

どうすればよいか? 47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

50 https://www.archunit.org/

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

他のプログラミング言語でのアーキテクチャテスト ● TNG/ArchUnitNET(C#) ● iternity/archlint.cs(C#) ● BenMorris/NetArchTest(.Net) ● sensiolabs-de/deptrac(PHP) ● carlosas/phpat(PHP) ● nazonohito51/dependency-analyzer(PHP) 55

Slide 56

Slide 56 text

アーキテクチャの典型的なテストの例 56

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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); }

Slide 60

Slide 60 text

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); }

Slide 61

Slide 61 text

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); }

Slide 62

Slide 62 text

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); }

Slide 63

Slide 63 text

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); }

Slide 64

Slide 64 text

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); }

Slide 65

Slide 65 text

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); }

Slide 66

Slide 66 text

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.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 67

Slide 67 text

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.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 68

Slide 68 text

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.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 69

Slide 69 text

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):

Slide 70

Slide 70 text

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):

Slide 71

Slide 71 text

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.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 72

Slide 72 text

72 Constructor (com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 73

Slide 73 text

73 Constructor (com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 74

Slide 74 text

74 Constructor (com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 75

Slide 75 text

75 Constructor (com.example.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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); }

Slide 78

Slide 78 text

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); }

Slide 79

Slide 79 text

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); }

Slide 80

Slide 80 text

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.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 81

Slide 81 text

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.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 82

Slide 82 text

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.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 83

Slide 83 text

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.infrastructure.datasource.EmployeeRepositor yImpl)> has parameter of type in (EmployeeService.java:0) Field has type 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)

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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); }

Slide 86

Slide 86 text

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); }

Slide 87

Slide 87 text

モノリスの発展性を支える アーキテクチャテスト 87 Photo by Markus Spiske on Unsplash

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

アーキテクチャの視座 ● モノリス内に存在する責務や関心事を適切な粒度で切り分け る ○ 大きな粒度:レイヤー、ドメイン ○ 小さな粒度:パッケージ、クラス ● それぞれの粒度で依存関係を注意深く設計する ○ モノリスを将来、マイクロサービスに分割するという観点では、ドメイン 間の依存関係(=実体としてはパッケージ間の依存関係)にはとくに注 目 89

Slide 90

Slide 90 text

依存関係を守り、育てる ● 「守る」手段としてのアーキテクチャテスト ● 「育てる」手段としてのアーキテクチャテスト 90

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

「育てる」手段としてのアーキテクチャテスト? 92

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

アーキテクチャテストの失敗は悪か? ● アーキテクチャテストはあるべき依存関係を表現したもの ● アーキテクチャテストは失敗しないはずのテスト ● アーキテクチャテストが失敗するとき ○ 実装誤り ○ アーキテクチャに対する発見のサイン ○ アーキテクチャについて議論する始点 94

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

最後に 97

Slide 98

Slide 98 text

アーキテクチャの目的 98

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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