Clean ArchitectureとEffで変更に強いAPIを設計する / API design by Clean Architecture and Eff

A967476c5855d593710a9a580f6b2aed?s=47 Yuichi Maekawa
September 25, 2020
1.6k

Clean ArchitectureとEffで変更に強いAPIを設計する / API design by Clean Architecture and Eff

モジュラモノリスで表現する複雑なドメイン領域と境界
https://scalamatsuri.org/ja/proposals/J14

PofEAAで読み解くSaaSバックエンドの現場のコード
https://scalamatsuri.org/ja/proposals/J26

The Clean Architecture
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

Freer Monads, More Extensible Effects
http://okmij.org/ftp/Haskell/extensible/more.pdf

A967476c5855d593710a9a580f6b2aed?s=128

Yuichi Maekawa

September 25, 2020
Tweet

Transcript

  1. Clean ArchitectureとEffで 変更に強いAPIを設計する scala.tokyo-online vol.1 #ScalaTokyo Maekawa Yuichi

  2. Maekawa Yuichi Twitter: @_kaelaela Software Engineer@ サブスクリプションビジネスの効率化 収益最大化プラットフォーム 初めてオンライン勉強会での登壇なので、 不手際などあるかもしれません

    :bow:
  3. Clean ArchitectureとEffを使った、変更に強いAPIの設計方法について なぜ必要なのか?どのように使うのか?概念的に説明します 話さないこと - REST API / RPC APIのAPIデザインの話ではないです

    :pray: - Clean Architectureの説明 - モナド、Effなどの数学的な説明 ※設計と書いてますが、設計はチームやプロダクトによると思うので、現時点でアルプが良いと考えているものという前提でお聞きください 今日の内容
  4. Clean ArchitectureとEffで 変更に強いAPIを設計する

  5. 変更に強いとは - 追加、変更、削除が容易 - 変更による影響範囲が小さい - 破壊的な変更が起きにくい

  6. APIは日々成長する - ドメインロジックの変更によるレスポンス内容の変更 - ユースケースの変更 - 新しいAPIの追加 - APIパラメーターの追加 -

    自社システム向けAPIと外部公開API - 片方の仕様変更に伴う追従 - 公開APIのサポート範囲拡大 などなど
  7. 変更を難しくする要因は2つ 避けることのできない要因 - ビジネスロジックの成長 (最初のドメインモデルでは対応できない) 技術・設計による要因 - テストがなく影響範囲の予測が不可能 - 依存方向の崩壊(スパゲティ)

    - 不適切な凝集度 - 大半が偶発的、論理的な凝集 チームや実装者による主観的なものが多い - 関心の分離ができていない - ビジネスと技術 - UIとデータ構造 など https://commons.wikimedia.org/wiki/File:Edsger_Wybe_Dijkstra.jpg
  8. Re: 変更に強いとは - 追加、変更、削除が容易 変更容易性 = ビジネスロジックの変更 * 技術・設計 -

    変更による影響範囲が小さい 変更量 = ビジネスロジックの変更 / 技術・設計 - 破壊的な変更が起きにくい ドメインモデルの安定性によって生まれる プログラマが作用できるのは技術・設計の部分
  9. アルプでは変更に強い設計を どう実現しているのか

  10. Scalebaseバックエンドシステム Application framework: System Design: Domain-driven Design, PofEAA Architecture: Moduler

    monolith, Clean Architecture, Library: Eff... e2e: Postman
  11. Scalebaseバックエンドシステム Application framework: System Design: Domain-driven Design, PofEAA Architecture: Moduler

    monolith, Clean Architecture, Library: Eff... e2e: Postman https://scalamatsuri.org/ja/proposals/J26
  12. Scalebaseバックエンドシステム Application framework: System Design: Domain-driven Design, PofEAA Architecture: Moduler

    monolith, Clean Architecture, Library: Eff... e2e: Postman https://scalamatsuri.org/ja/proposals/J14
  13. Scalebaseバックエンドシステム Application framework: System Design: Domain-driven Design, PofEAA Architecture: Moduler

    monolith, Clean Architecture, Library: Eff... e2e: Postman この辺がどうシステムに寄与しているかという話
  14. Clean Architectureで 何を解決したいのか https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

  15. Clean Architectureで疎結合・高凝集度なコードへ 依存方向の崩壊: 依存方向を一方向にする 不適切な凝集度: レイヤーごとに依存を絞る、ユースケースを分離 関心の分離: ビジネスロジック(ドメイン)の分離、カプセル化 ↓ レイヤーが分かれ、疎結合・高凝集度になったコードは変更が小さく、簡単になる

  16. Scalebaseは一般的なレイヤードアーキテクチャ Clean Architecture in Scalebase BatchAdapter Http Adapter ExternalHttp Adapter

    Domain UseCase Web UI DB Other Context Grpc Adapter Entity Layer Domainモデル置き場。依存はなし UseCase Layer アクターベースのユースケース Interface Adapter Layer HttpはAPI用、Secoundaryは Repository用 Grpcは内部通信用 Frameworks and Drivers Layer DB、インフラ、Webなどと繋がる Secondary Adapter
  17. UseCase/Interface Adapterが時間的凝集度により異なる効果を持つ処理が集まりやすい APIの変更はClean Architectureのどこで起こるのか BatchAdapter Http Adapter ExternalHttp Adapter Domain

    UseCase Web UI DB Other Context Grpc Adapter UseCase Layer アクターベースのユースケース Interface Adapter Layer HttpはAPI用、Secoundaryは Repository用 Grpcは内部通信用 Secondary Adapter
  18. sbtでClean Architectureの単一依存方向性を実現する sbtのMulti Project buildでコンテキスト * レイヤーで分割し、依存方向を絞る Interface Adapter ->

    UseCase -> Domain 基本的にこれだけでよい 厳密にいうとDIP(dependency inversion principle)も行っている
  19. UseCaseやInterface Adapterから技術的な関心を分離できているか? UseCaseの処理例 - モデルのCRUD・DBアクセス・異なるデータソース呼出・APIコールなど Interface Adapterの処理例 - 認証・ユースケースの実行・モデル変換・IOなど Clean

    Architectureだけでは完全に分離できない そこで... Eff
  20. Effで何を解決したいのか http://okmij.org/ftp/Haskell/extensible/more.pdf

  21. 殆どのソフトウェアは非同期処理+成功/失敗 - HTTPリクエスト - DBアクセス など Scalaでは 非同期処理: Future or

    monix.Taskなど 成功/失敗: Try or Either[L,R]など 非同期の結果成功/失敗: Future[Either[L,R]] = モナドの合成
  22. モナドの合成が難しい?

  23. for-yield式 for { // この中ではすべてmap,flatMap, withFilter(case match + if)の糖衣構文 //

    基本的には一つのコンテキストのみ対象(ex, Option, Future, Eitherなど) // この中で技術的な関心の副作用がプログラムを複雑にする // 副作用 ≒ 状態の変更、IO、例外、execution context、Schedulerなど } yield ...
  24. モナド合成の問題 僕らはこれをモナドトランスフォーマーではなくEffで解決している Future[Error Either Unit]コンテキスト Error Either Option[Model]コンテキスト

  25. Eff(Extensible Effects) 正確にはExtensible Effectsを改善したMore Extensible Effectsを採用 Eff一つの型で複数の効果を表現できるモナド なぜ必要なのか より技術的な部分の関心を分離する 「Scalaを使う

    ≒ モナドを使う」(IMO)とき、合成が難しいという技術的制約 モナドの合成を最もシンプルに書ける(フラットでliftがない)
  26. ケーススタディ: DryRun あるAPIでデータの永続化をしないけど通るかチェックする

  27. ユースケースの凝集度が時間的凝集から論理的凝集になり悪手 1.dryRunフラグで判定

  28. 2. ほぼ同じdryRunユースケースを追加 実装は楽 - 小さなユースケース、少ないユースケースなどの場合は良さそう - UseCaseの重複が増える: ヒューマンエラーによるバグを生みやすい - UseCaseの修正が常に二倍になる:

    変更量を増加させてしまう
  29. 3. EffでdryRunのInterpreterを作る UseCase Layer: Read/Write用のInterpreterはすでにある 実行時(run)にすべてRollbackする処理を追加することで実現する - UseCaseの処理を一切変更しなくてよい - UseCase内に技術的関心による分岐や変換がなくなる

  30. 3. EffでdryRunのInterpreterを作る Interface Adapter Layer: Controller/Presenter層などで分岐処理を書きdryRunするのみ ユースケースの差し替えなどはなし

  31. Effを使うメリット - モナドの合成が簡単になる - UseCase/Interface Adapterから殆どの技術的関心を分離でき、自然言語の表現に近づく - 効果をContext Boundで表現でき、何が起こるかひと目で判断できる ->

    DBアクセスが起こり得る処理なのだな FYI: Context Bound def f[T](...)( implicit a:A[T] ) -> def f[T:A](...)と書ける糖衣構文
  32. Effを使うデメリット - 慣れるまでに時間がかかる(僕は大したデメリットではないと思う) - コンパイルエラーにはなるが、エラーが難解 - アーキテクチャとの話が少ない - 導入事例が少ない -

    UseCaseなどをより綺麗に記述するためには全ての層でEffを返す必要がある
  33. 変更量を下げ、変更容易性を上げるためにClean ArchitectureとEffを使う Clean Architecture - モジュール化、単一依存方向を実現し、疎結合・高凝集度にする - sbtのMulti Package buildで簡単に実現できる

    Eff - モナドの合成を簡単にする - Clean Architectureだけではできない技術的関心の分離を担う Thank you!