巷で見かけるClean Architectureのコード例に対するモヤモヤについて

たまにはふわっとしたことをふわっと書いてみるかということで書いてみた。

はじめに

Clean Architectureについての記事をたまに見かけることがある。多くは自分の馴染んだ言語でこのように実装してみましたと言う例なのだけれど、多くの場合にモヤモヤを抱えた状態の読後になる。

なぜそのような読後感になるかいろいろ考えたのだけれど、シンプルに「受け入れテストを満たしていないのではないか?」と思ってしまうからというのが答えだった。

Clean Architectureによって得られるもの

Clean Architectureについて触れられている記事では、利点として以下の様なことが書かれている事が多い。

  1. Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.
  2. Testable. The business rules can be tested without the UI, Database, Web Server, or any other external element.
  3. Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules.
  4. Independent of Database. You can swap out Oracle or SQL Server, for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.
  5. Independent of any external agency. In fact your business rules simply don’t know anything at all about the outside world.

簡潔にまとめるならフレームワーク・UI・永続化の方法・外部とのやり取りの実装に対して非依存ということ。そしてテスタブル。

これを求めた実装例の1つがいわゆるN個の円で描かれたアーキテクチャなのだから、ぶっちゃけた話、どのコードが円のどの部分にあたるかを説明したところで片手落ちなのではないか?と思う。インスピレーションに感銘を受けて手を動かせば、その出力は元々の要件を満たしているか?というとそうではないと思うというような事を気にしているようだ。

あなたのコード例は受け入れテストを通っているか?

ここでClean Arcitectureの実装例に対する受け入れテストについて考えてみる。

個人的には、受け入れテストには、「ある機能を要望したときに、定義した振る舞いに対して完全であること」というような意味合いで捉えているようだ。

そして得られる利点の部分を考慮すれば、受け入れテストを通っているかを真面目に気にするなら、先程の5つの基準全てに対応していることが求められる。が、まぁそれはめんどくさいし、一つ一つのコード例に対して求めるものとしては大げさ過ぎるように思う。

それでは、求める最小限度は何か?ということを考えてみることにする。先程の基準は、少し言い方を変えれば、「あるAを利用したらBが得られる」という表現に変えることができる。これの対遇を取れば「Bが得られないなら、Aを利用していない」ということになる1。言い換えればこういうことになる。

  • 特定のフレームワークに依存していたら、Clean Architectureではない
  • 特定のUI実装に依存していたら、Clean Architectureではない
  • 特定の永続化の方法に依存していたら、Clean Architectureではない
  • 特定の外部とのやり取りの実装に依存していたら、Clean Architectureではない
  • テスタブルでないなら、Clean Architectureではない

受け入れテストを通っているということは、せめてこれらのうちのどれかを満たしていることを指すのではないか?というような思いがどうやらあるようだ。

実装例に対して求めるものは何か?

書き下してみると、実装例に対して自分が何を求めていたのかが少し明確になってきたような気がする。つまりテストをしてもらいたい。別な言い方をするなら、受け入れテストを満足していることが確認できるような対応も含めての実装例ということになるのではないかと言うことらしい2。そしてそれが不足している。

例えばの例を考えてみる。よくあるコード例の一つとして「TodoアプリをClean Architectureで実装してみました」というのが挙げられるかもしれない。これに関してはコード例に以下を含めて欲しいということかもしれない。

  • 複数のフレームワークを同時に利用する例
  • 複数のUI実装を同時に利用する例
  • 複数の永続化を同時に利用する例

外部の話やテスタブルは一旦省略3。ここで「同時に利用する」と言うのは、あるフローで同時に利用しているというわけではなく、コードの改変なしに複数の形態での機能を提供できるというようなもの。

この内どの例に対応しているものが見たいかということも考えてみる。

複数のフレームワークを同時に利用する例を考えてみると、これは、時系列的に長めのスパンで捉えてみたときに遭遇することはあるかもしれないが、ある瞬間で切り出したときにあまり良い例ではないのではないか?と思ったりした。

次に、複数の永続化の方法に同時に対応することを考えてみると、Repositoryなどの実装でよくこれを考慮する例を見かけるが、あんまり気にすることではないような気がする。初期の利用を決めたDBからの移行はそうそうないのではないか?4

というふうに考えてみると、複数のUI実装を同時に利用する例を考えるのが妥当なのではないかと思う。大げさに考えてみれば、複数のUI実装への対応の一部に複数のフレームワークへの対応が含まれることもあるだろうし。

例えばの例について考えてみる

複数のUIについて対応するということを考えてみるとして、どのような形態での提供を考えると良いのだろう?

たいていの場合によく挙げられる1つ目の形態は「web APIとしての実装」かもしれない。ここで、ネイティブアプリもウェブアプリも同様のweb APIを利用して作ることができるので、1つの形態と見做すことにする(もちろんBFFなど切りたくなる場合もあるが)。

そう考えると例えば別の形態として以下の様なものが挙げられる。他にもいろいろあるかもしれない。

  • CLIのコマンド
  • repl (interactive shell)
  • chat bot
  • GUIアプリ

下に行くほど面倒な例になる。個人的にはrepl辺りから始めるというのが綺麗なのではないか?と思う。 本当に初期も初期から考えるなら、全くテストのことも考えていないようなコード例から初めて見るのが実践としては有用なのではないかと思う。

例えばこの様なところから。

実行主体の多様性

そうそう、個人の探求としては、テストしやすさなどを気にしてコードを切り分けるところから始めるのが正解なのではないか?と思ったりした。何より個人的に欲しくなるのは、特定の概念になぞらえてマッピングすることではなく、現実の対象に当てはめて不要なところは省きつつ、元々欲しかった利点を享受することだったりするわけだし。

そしてこれは「テストコードについて考えることが良い設計を導く」というよくある話とも関連していて、要はこれも実際のアプリケーションとしての実行とテストとしての実行と複数の形態での実行が考慮されているから良いということになるのではないかと思う5

この種のことを個人的には「実行主体」と読んでいる。そしてこれの多様性に対する自由度と制限(本来的には不要なことについては考えない)を気にできればそれで十分なのではないか?と思ったりした。逆に言えばそれについて考えていなければ意味がないのではないか?と思ったりした。

まとめ

まとめは以下。

  • その Clean Architecture のコード例、受け入れテストしっかりしている?
  • 「実行主体の多様性」について考えるだけで良いのではないか?

という話でした。

周辺のつぶやき


  1. 量化は無視。まぁ一部が稀に存在するというだけでは困るので、全てのが暗黙に含まれていて良いと思う。

  2. ユーザー(発注した側)は読者で、実装者(受注した側)が実装したものがコード例と捉えると、検証の義務は読者にあるという考え方もできなくはないが。。

  3. ぶっちゃけた話、5つ全部をテスタブルで括ってしまうこともありといえばありだし。前提条件とする。

  4. というよりも、綺麗にinterfaceとして切り出されていても、現実的には対応する書き換え対象の数が膨大になるので一筋縄ではいかない

  5. ユニットテスト結合テストと言う形でより細かく考えても良いかもしれない