Upgrade to Pro — share decks privately, control downloads, hide ads and more …

実践 Clean Architecture

実践 Clean Architecture

yoshiyoshifujii

January 19, 2019
Tweet

More Decks by yoshiyoshifujii

Other Decks in Technology

Transcript

  1. Yoshitaka Fujii @yoshiyoshifujii Chatwork 株式会社(5 ヶ月目) Scala 歴 5 年目

    Scala 関西 Summit スタッフ(4 年目) 登壇 ScalaMatsuri2016 Scala でドメイン駆動設計に真正面から取り組んだ話 ScalaMatsuri2017 Serverless Architecture をScala で構築するぞ Scala 福岡2017( 懇親会LT) Scala とサーバレス相性いいよ アミノ酸とプロテイン 自己紹介 実践 Clean Architecture Scala 福岡 2019 2 / 46
  2. 著者 Robert C. Martin ( アンクル・ボブ) 1970 年からプログラマ cleancoders.com の共同創業者であり、ソフトウェア開発者向けの学習用動画をオンラインで提供している

    TheCleanCoder (邦訳:CleanCoder─ プロフェッショナルプログラマへの道) CleanCode (邦訳:CleanCode─ アジャイルソフトウェア達人の技) UMLforJavaProgrammers (邦訳:Java プログラマのためのUML ) AgileSoftwareDevelopment (邦訳:アジャイルソフトウェア開発の奥義) ExtremeProgramminginPractice (邦訳:XP エクストリーム・プログラミング実践記─ 開発現場からのレポート) Robert C .Martin, 角 征典, 高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (Japanese Edition). Kindle 版. 実践 Clean Architecture Scala 福岡 2019 4 / 46
  3. The Clean Architecture [2012/08/13] The Clean Architecture ­ The Clean

    Code Blog https://blog.cleancoder.com/uncle­bob/2012/08/13/the­clean­architecture.html [2013/07/13] アーキテクチャの目的は意図であり、フレームワークではない ­ InfoQ https://www.infoq.com/jp/news/2013/07/architecture_intent_frameworks [2015/01/05] 持続可能な開発を目指す ~ ドメイン・ユースケース駆動(クリーンアーキテクチャ) + 単方向に制限した処 理 + FRP ­ Qiita https://qiita.com/kondei/items/41c28674c1bfd4156186 [2015/12/22] まだMVC,MVP,MVVM で消耗してるの? iOS Clean Architecture について ­ Qiita https://qiita.com/koutalou/items/07a4f9cf51a2d13e4cdc [2016/03/16] DDD + Clean Architecture + UCDOM Full 版 ­ Speakerdeck https://speakerdeck.com/yoskhdia/ddd­plus­clean­architecture­plus­ucdom­fullban https://github.com/yoskhdia/cleanArchSample [2018/11/07] ドメインオブジェクトを中心としたClean Architecture のためのレイヤー構成 ­ Qiita https://qiita.com/j5ik2o/items/7817055b35b2ff34f2e9 [2018/12/17] 実装クリーンアーキテクチャ ­ Qiita https://qiita.com/nrslib/items/a5f902c4defc83bd46b8 実践 Clean Architecture Scala 福岡 2019 5 / 46
  4. アジェンダ 1. なぜ Clean Architecture か 2. ポリモーフィズム 3. SOLID

    原則 4. コンポーネントの原則 5. Clean Architecture 実践 Clean Architecture Scala 福岡 2019 6 / 46
  5. 1. なぜ Clean Architecture か あらゆるソフトウェアシステムは、 「方針」 と 「詳細」 に分割できる

    方針 は、ビジネスのすべてのルールや手順、システムの本当の価値 詳細 は、DB 、WEB 、フレームワークなど方針に影響を与えるものではない ソフトウェアをソフトに保つために、できるだけ長い期間、多く選択肢を残す 残すべき選択肢とは、 重要でない詳細 方針と詳細を慎重に区別し、方針が詳細に依存せず、詳細の決定を延期・留保できる方針をデザインする 優秀なアーキテクトに求められる 実践 Clean Architecture Scala 福岡 2019 7 / 46
  6. 1. なぜ Clean Architecture か レイヤー化アーキテクチャ、ヘキサゴナルアーキテクチャも目的は同じ「関心事の分離」 フレームワーク非依存、テスト可能、UI 非依存、データベース非依存、外部エージェント非依存 ドメイン駆動設計によるモデル分析の結果を中心で表現し、詳細を後から選択する 叫ぶアーキテクチャ

    アプリケーションのアーキテクチャが「ヘルスケアシステム」「会計システム」と叫ぶか ドメインとユースケースは相互関係 優れたクリーンなアーキテクチャと設計により 長期的に利益をもたらすシステムを構築する 実践 Clean Architecture Scala 福岡 2019 8 / 46
  7. 2. ポリモーフィズム OO 言語が安全で便利なポリモーフィズムを提供しているということ ソースコードの依存関係は ( たとえどこにあっても) 逆転できる 依存関係逆転の原則 (DIP)

    このパワーを使って、DB やUI をビジネスルールに依存させることができる ビジネスルールを独立してデプロイできる 独立してデプロイできるなら、別々のチームで開発する独立開発可能性がある 実践 Clean Architecture Scala 福岡 2019 11 / 46
  8. 3. SOLID 原則 単一責任の原則(SRP :Single Responsibility Principle ) モジュールは、たったひとつのアクターに対して責務を負うべき オープン・クローズドの原則(OCP

    :Open Closed Principle ) 拡張に対しては開いていて、修正に対して閉じていなければならない 変更の影響を受けずにシステムを拡張しやすくする システムを分割して、依存関係を階層構造にする リスコフの置換原則(LSP :Liskov Substitution Principle ) クラスA はクラスB を使っていて、そのB をC に置き換えた際にA の振る舞いが変わらない場合、C はB の派生形である 置換可能性に少しでも違反すると、特別な仕組みだらけになる インターフェイス分離の原則(ISP :Interface Segregation Principle ) 必要としないお荷物を抱えたものに依存していると、予期せぬトラブルの元につながる 依存関係逆転の原則(DIP :Dependency Inversion Principle ) ソースコードの依存関係が抽象だけを参照しているものが、最も柔軟なシステムだ 実践 Clean Architecture Scala 福岡 2019 13 / 46
  9. 4. コンポーネントの原則 再利用・リリース等価の原則 (REP) 再利用の単位とリリースの単位は等価になる コンポーネントを形成するクラスやモジュールは凝集性のあるグループ コンポーネントには一貫するテーマや目的があり共有するモジュールを集める Maven 等にリリースする際の凝集性は一貫性がないとだめ 閉鎖性共通の原則

    (OCP) 同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめること 変更の理由やタイミングが異なるクラスは、別のコンポーネントに分けること 単一責任の原則(SRP) をコンポーネント向けに言い換えたもの 全再利用の原則 (CRP) コンポーネントのユーザに対して、実際には使わないものへの依存を強要してはいけない インターフェイス分離の原則(ISP) を一般化したもの 不要なものには依存しないこと 実践 Clean Architecture Scala 福岡 2019 14 / 46
  10. 境界線を越える 円の境界線をどのように越えるべきかの例 Controller とPresenter は、内側のレイヤーのUse Case と通 信している Controller ­>

    Use Case ­> Presenter 依存関係は、すべて内側のUse Case に向いている 依存関係逆転の原則(DIP )を使う インタフェースや継承を整理して制御の流れと逆転するよ うにする 依存性のルールに違反しないようにポリモーフィズムを活 用する 5. Clean Architecture 実践 Clean Architecture Scala 福岡 2019 23 / 46
  11. 典型的なシナリオ ユーザーからの入力データを、Controller に渡す Controller は、POJO なInput Data にデータを詰め込む Input Boundary

    を経由して、Use Case Interactor に渡す Use Case Interactor は、Entities のダンスを制御する Data Access Interface を使用してDatabase とI/O する Output Data をPOJO として生成し結果を詰め込む Output Data は、Output Boundary インタフェースを経由し て、Presenter に渡す Presenter は、Output Data をViewModel(POJO) に詰め込み 直す 5. Clean Architecture 実践 Clean Architecture Scala 福岡 2019 24 / 46
  12. プロジェクト構成 build.sbt lazy val `infrastructure` = (project in file("modules/infrastructure")) lazy

    val `entities` = (project in file("modules/entities")).dependsOn(infrastructure) lazy val `usecases` = (project in file("modules/usecases")).dependsOn(entities) lazy val `adapters` = (project in file("modules/adapters")).dependsOn(usecases) lazy val `main` = (project in file("modules/main")).dependsOn(adapters) 実践 Clean Architecture Scala 福岡 2019 27 / 46
  13. プロジェクト構成 build.sbt lazy val `infrastructure` = (project in file("modules/infrastructure")) lazy

    val `entities` = (project in file("modules/entities")).dependsOn(infrastructure) lazy val `usecases` = (project in file("modules/usecases")).dependsOn(entities) lazy val `adapters` = (project in file("modules/adapters")).dependsOn(usecases) lazy val `main` = (project in file("modules/main")).dependsOn(adapters) infrastructure は、レイヤー化アーキテクチャのインフラ層ではなく、要件が変化しても依存し続ける層。すべての 層が依存できる汎用的な技術基盤。 entities は、その名の通りエンティティ。 usecases も、そのまま。 adapters も、そのまま。 main は、究極的な詳細が入るところ。依存関係は、このmain プロジェクトに注入する。 sbt のマルチプロジェクトで、依存の方向性を強制する 実践 Clean Architecture Scala 福岡 2019 27 / 46
  14. 依存するライブラリ entities https://github.com/j5ik2o/scala­ddd­base "com.github.j5ik2o" %% "scala-ddd-base-core" % version DDD の集約とリポジトリを良い感じに抽象化したcore

    ライブラリ Slick(JDBC) 、SkinnyORM(JCBC) 、Redis などボイラープレートとなりがちな具象部分を提供してたり https://github.com/septeni­original/sbt­dao­generator を使ってDAO を自動生成して組み合わせたり ボイラープレートが増えがちな詳細部分の作業を減らしてくれる めちゃオススメ 実践 Clean Architecture Scala 福岡 2019 28 / 46
  15. Use Cases usecases パッケージ直下に、 InputBoundary 、 OutputBoundary 、UseCaseInteractor 、を定義して いる

    抽象度が高い作りなので、別のプロジェクト、パッケ ージに切り出すのも有り ユースケースのアクターごとにパッケージを切る OAuth クライアントを管理するアクターのユースケー スを、 admin 認可フローを実施するアクターのユースケースを、 authorization usecases パッケージ の下を見ると、このシステムが何 か叫んでいるようにしたい パッケージ構成 実践 Clean Architecture Scala 福岡 2019 31 / 46
  16. Use Cases gateway は、アプリケーションロジックなので、 usecases プロジェクトに置いている Repository っていうと、レイヤー化アーキテクチャとかだ ったら、ドメイン層に置いていた Clean

    Architecture では、ビジネスロジックを entities 、アプリケーションロジックを usecases としているの で、永続化するとかどうこうはアプリケーションロジック と判断した パッケージ構成 実践 Clean Architecture Scala 福岡 2019 32 / 46
  17. Adapters Framework や、DB など、詳細な部分との変換処理が入る ところ Controller 、 Presenter をそれぞれ、 controllers

    、presenters にまとめる gateway には、Repository の具象を置く Contoroller は、仮にAkka HTTP でWeb を作ると決めた のであれば、 akka.http.scaladsl.Route を返すよう なメソッドを持つ Presenter は、POJO なViewModel を定義して返す Controller で、Response の形式、仮にJSON を返却す る場合、 Circe ( キルケー) などのJSON Library にPOJO な ViewModel からコンバートするような処理を持つ パッケージ構成 実践 Clean Architecture Scala 福岡 2019 33 / 46
  18. Adapters adapters プロジェクトは、さらに細分化しても良い 仮に、AWS Lambda などのFaaS にデプロイするようなモ ジュールである場合、adapters のような詳細なライブラ リとの依存の強いプロジェクトはファイルサイズが巨大に

    なりがち。 依存ごとに細かくプロジェクトを分けることも考えられる が、詳細については、後で良い なるべく、決定を遅らせていく 仮にここをあとからごちゃごちゃ変えても、方針は影響を 受けない パッケージ構成 実践 Clean Architecture Scala 福岡 2019 34 / 46
  19. 典型的なシナリオで述べた以下について表している Controller は、POJO なInput Data にデータを詰め込む Input Boundary を経由して、Use Case

    Interactor に渡 す InputData は、型パラメータとして任意の型を扱う Controller からは、InputBoundary を経由してInputData を渡 して終わるので、Unit を返す InputBoundary <I> trait InputBoundary[InputData] { def execute(arg: InputData): Unit } 実践 Clean Architecture Scala 福岡 2019 36 / 46
  20. 典型的なシナリオで述べた以下について表している Use Case Interactor は、Entities のダンスを制御する Output Data をPOJO として生成し結果を詰め込む

    UseCaseInteractor は、InputBoundary を継承している UseCaseInteractor は、OutputBoundary を保持している UseCaseInteractor は、InputData を受け取りdance して OutputData を返す dance を同期でするか非同期でするかとか、そういった中 身は詳細なので、Higher Kind で返却される OutputData は、OutputBoundary に渡される UseCaseInteractor trait UseCaseInteractor[F[_], InputData, OutputData] extends InputBoundary[InputData] { protected val outputBoundary: OutputBoundary[F, OutputData] override def execute(arg: InputData): Unit = outputBoundary.onComplete(dance(arg)) protected def dance(arg: InputData): F[OutputData] } 実践 Clean Architecture Scala 福岡 2019 37 / 46
  21. 典型的なシナリオで述べた以下について表している Output Data は、Output Boundary インタフェースを経 由して、Presenter に渡す OutputBoundary を継承するのは、Presenter

    OutputBoundary を経由して、Presenter にOutputData が渡 される F[_] は詳細なので、Higher Kind に抽象化されて渡されて いる OutputBoundary <I> trait OutputBoundary[F[_], OutputData] { def onComplete(result: F[OutputData]): Unit } 実践 Clean Architecture Scala 福岡 2019 38 / 46
  22. UseCase の実装例 class AuthorizationAuthorizeUseCase[F[_]]( override protected val outputBoundary: OutputBoundary[F, AuthorizationAuthorizeOutput],

    private val clientRepository: ClientRepository[F], private val reservedAuthorizationRepository: ReservedAuthorizationRepository[F] )(implicit ME: UseCaseMonadError[F]) extends UseCaseInteractor[F, AuthorizationAuthorizeInput, AuthorizationAuthorizeOutput] { override protected def dance(arg: AuthorizationAuthorizeInput): F[AuthorizationAuthorizeOutput] = ... } Use Case 1 つにつき、1 クラス インターフェイス分離の原則(ISP ) 余計な依存を持たない コンストラクターにOutputBoundary やRepository の抽象を受けとる 実際に注入するのは、main プロジェクト Airframe でコンストラクターインジェクションすると良さげ 実践 Clean Architecture Scala 福岡 2019 39 / 46
  23. UseCase の実装例 sealed trait UseCaseError case class UseCaseSystemError(cause: Throwable) extends

    UseCaseError case class UseCaseApplicationError(message: String) extends UseCaseError type UseCaseMonadError[F[_]] = MonadError[F, UseCaseError] UseCase では、Entities の操作をした結果、ApplicationError と判断されるものと、何らかのシステム異常による SystemError を扱う このUseCaseError は、Presenter でパターンマッチし、WEB であれば、4xx エラーや5xx エラーに変換される 同期か非同期かとか、Either かTry といった詳細について、UseCase を表現する際に気にしない 詳細を決める際に、注入するため、Tagless Final Style で実装する ただし、前述の通り、UseCase では、エラーを扱う必要があるため、MonadError で型を制約する 実践 Clean Architecture Scala 福岡 2019 40 / 46
  24. UseCase の実装例 override protected def dance(arg: AuthorizationAuthorizeInput): F[AuthorizationAuthorizeOutput] = for

    { clientId <- ME.pure(ClientId(arg.clientId)) // arg.clientId について検査が必要かどうかは、ClientId のassert で表明する client <- clientRepository.resolveById(clientId) reservedAuth <- client .reservedAuthorization( // OAuth クライアントから予約済み認可を生成する responseType = arg.responseType, redirectUri = arg.redirectUri, scope = arg.scope, state = arg.state ).toM[F] // EntitiesError からMonadError[F, UseCaseError] に変換する _ <- reservedAuthorizationRepository.store(reservedAuth) } yield AuthorizationAuthorizeOutput( redirectUri = Some(reservedAuth.redirectUri.value), scope = Some(reservedAuth.scopes.toStringList), state = reservedAuth.state.map(_.value), refKey = Some(reservedAuth.id.value), clientId = client.id.value, clientName = client.name.map(_.value) ) 実践 Clean Architecture Scala 福岡 2019 41 / 46
  25. Entities ­ ビジネスロジック def reservedAuthorization(responseType: String, redirectUri: Option[String], scope: Option[Seq[String]],

    state: Option[String]): EntitiesValidationResult[ReservedAuthorization] = (assertResponseType(responseType), assertRedirectUri(redirectUri), assertScope(scope), assertState(state)) mapN { case (_responseType, _redirectUri, _scope, _state) => ReservedAuthorization( id = ReservedAuthorizationId(RefKeyGenerator.generate.value), status = Status.Active, responseType = _responseType, clientId = this.id, redirectUri = _redirectUri, scopes = _scope, state = _state, createdAt = ZonedDateTime.now, updatedAt = None ) } EntitiesValidationResult は、Type エイリアス assertXXX で事前条件を確認している 実践 Clean Architecture Scala 福岡 2019 42 / 46
  26. EntitiesValidationResult case class EntitiesError(message: String) type EntitiesValidationResult[A] = ValidatedNel[EntitiesError, A]

    ValidatedNel のあたりは、Either にするとか、Invalid を貯めて返却したいといった要件やシステム上の決めに対して合わせ て型を決める 今回の例では、事前条件を確認した結果、エラーとなる原因を貯めて一括で返却するとした このあたりの選択は、方針として、先に決める必要がある 実践 Clean Architecture Scala 福岡 2019 43 / 46
  27. まとめ あらゆるソフトウェアシステムは、 「方針」 と 「詳細」 に分割できる Clean Architecture を学び実践すると、方針と詳細を慎重に区別し、方針が詳細に依存せず、詳細の決定を延期・留保でき る方針をデザインできそう

    方針をどんどんコードに落として検証、検査、議論し価値を実現 方針ができたら、詳細を作り、プラガブルにソフトなアーキテクチャを実現する 実践 Clean Architecture Scala 福岡 2019 44 / 46