前回、Clean Architecture
でよく出てくる依存性逆転の原則
で使われるDIコンテナをPythonで使う方法を確認しました。
次に、プロジェクトのフォルダ構成をClean Architectureに沿って検討してみようと思います。
とはいっても、自分は特にClean Architecture
やDDD(Domain Driven Design)
に関する書物を読んだわけではなく、ググってこんな感じかな?といういわゆるオレオレClan Architecture
です。
特に、実装クリーンアーキテクチャの内容を色濃く受けています。
数百ページの理論を学ぶより、実際に構築していく中で、やっぱり体で覚えていこうと思います。
色々やってみた系のブログを読むと、チーム内でも設計思想の違いから結構衝突が起きてるそうなので、どっちかというとコーディングルールのようにチームで体系だてられたものを作ってみんなで共通理解をするのが良い使い道そうです。
まず、このアーキテクチャを使う目的を自分の中でしっかりと持っておきます。
そうでないと、作っていく中で自分の中でもブレが出てきて頓挫してしまいます。
また、目的に沿っていれば途中で構成を変えたいときも容易だと思います。
私はClean Architecture
を主に以下の目的で使います。
特に自分はそれまでPythonでデータ収集のスクリプトなどを作成していたのですが、とにかく開発速度を要求されることが多く、機能としても
くらいしかなかったので、基本的に1ファイルの中の1つの関数
で作成していました。
もちろんクラスなんてほぼ使わないです。クラスなんて前職の新人研修のJava研修以来ほぼ使ってない状況でした。
ただ、やっぱりそんなコーディングだと、動いているときはいいですが、後の仕様変更やバグ改修をするときに全ての構造を把握していないといけないので結構しんどい思いをしたのを覚えています。
もちろん自動テストできる状況ではなく、実際に動かしてみないとわからない状況だったので、リリースするときとかヒヤヒヤもんです。
なので、Clean Architecture
を採用するときは、少なくともこれら2点が解決されていること目指します。
例えば、上記の例だと、3つの機能ごとにオブジェクトとして分解して、それぞれの機能でテストができることを目指します。
フォルダ構成を検討するにあたって、この後出てくる用語について私が理解している範囲で説明します。
それぞれの用語の正式な意味については、ググった方が出てくるかと思いますが、難しい理論的なお言葉がつらつらと書かれていて中々理解できなかったので、たぶんこういう意味でいいよね?くらいのノリで解釈して扱っています。
ここらへんも、実際に作っていく中でそれぞれの用語の正しい理解ができていければいいなぁと思います。
DDD
の最初のDのやつです。嘘です、そんなに詳しくないです。
ただ、Domainというものがビジネス側
で定義する最も重要な定義みたいです。
Clean Architecture
やDDD
を調べていると、よくビジネス側
とか開発側
みたいな言葉を目にします。
ビジネス側というのは、例えばソフトウェア開発については素人だけど、そのソフトウェアを使う分野に長けている人のことを指します。
銀行システムを作るとしたら、ビジネス側の人は倍返しが好きそうな銀行の行員さんです。
というのも、DDD
の思想として、Domain
は開発者側だけではなく、ビジネス側の人間にも理解できるように設計するというのがあります。
また、Clean Architecture
だとEntity
の外側にあるUseCases
までがビジネスルールと定義しているので、ある程度そのソフトウェアの肝となる部分についてはついては普通の人が見ても分かりやすいように作ってあげる必要があります。
で、このDomain
ってなんやねんというのがいまだに自分の中でも腑に落ちていないのです。
例えば、NPBの選手データを取得してデータベースに保存したいソフトウェアを構築するときは、Domain
は選手データがDomainにあたると思います。
そして、その選手データにある選手名はもちろん、所属チームやポジションといった情報の定義をここで記載しておきます。
また、この選手データは何ができるのかということも定義しておきます。
ここだと、データベースに保存できれば良いので、保存機能の定義だけしておきます。
このような選手データの情報についてをEntity
、保存機能についてをRepository
と今回は定義しました。
特に、Repository
についてはデータを永続化(つまり保存)するケースについて用いる用語のようで、他には出力したいときにはPresenter
というのを用いたりするようですが、ここらへんは詳しくないです。
ここでは、名の通りこのソフトウェアは何ができるのかというのを記載します。
UseCases
で何ができそうなのかを抽象的に記載しておいて、Interactor
でそれを具体化できるようにします。
ビジネス側
の人はこのUseCases
で何ができるかを何となく把握できればよく、開発側
の人は人知れずInteractor
で中身を作るような関係です。
実際に開発していると、UseCases
で抽象化しておくことで、各機能と切り離せたり、DIコンテナを注入する隙を作れるようになると実感しました。
また、このInteractor
で何かやるときにはだいたい何らかの入力(input)が必要でそれについては Inputdata
として別途定義しておきます。
ブログとか見ていると、このInputdata
はDTO
みたいなものという記載があるので、最初は正直Entityとの違いが分からなかったのですが、Inputdata
はとにかくInteractor
を動かすためにどういう入力が必要かに注力して考えるのが良いと思います。
ここまでくるとビジネス側
の人はあまり興味をもつ必要はなく、あとは開発側
がDomain
とApplication
をうまいこと動かすことができるように、ソフトウェア分野とDomainをAdaptさせてあげるような機能をゴリゴリ定義していきます。
例えば先に出てきたNPBの選手データを取得してデータベースに保存したいソフトウェアの場合は、Webからデータをとってくるとこや、データをデータベースに保存するところとなります。
この層のワードについてもあまり良く理解していないのですが、Inputになりそうな機能、その後Interactor
に渡したい機能にはController
に、データベースなどに関わる機能についてはGateway
に割り振ろうと思います。
特に、データベースからデータを取得してそれをInputとするような機能はController
にすべきか、Gateway
にすべきかはまだ迷っていますが、Clean Architecture
の図的には、Gateway
の方が近いのかな?というのが今の所の所感です。
一番外側の機能になり、ここにフレームワークや外部ライブラリを定義すると良いと言われています。
正直まだここの使い所は掴めていないのですが、例えばGateway
でデータをPostgresに保存する機能を作った場合は、そのPostgresの接続情報やclientはInfrastracture
で定義しておくことで、DBの接続先とかを楽に変えれたり、Gateway
ではそういったことを気にしなくなるので良いのかな?と思っています。
以上を踏まえて、一旦以下のようなフォルダ構成を検討しています。
- src
- domain
- <damain a>
- <damain a>_entity.py
- i_<domain a>_repository.py
- apllication
- <domain a>
- <function a>
- <function a>_<domain a>_inputdata.py
- i_<function a>_<domain a>_usecase.py
- <function a>_<domain a>_interactor.py
- adapter
- <domain a>
- controller
- <domain a>_controller.py
- gateway
- <domain a>_<DB名など>.py
- infrastractor
- <db>
- i_<db>_handler.py
- <postgresなど>
- <postgresなど>_handler.py
domainフォルダとapplicationフォルダについては個人的にも結構しっくりきています。
逆にadapterフォルダやinfrastractorについてはまだまだしっくりきていないです。
次は実際に作ってみるのを通して、この構成でどれだけ目的を達成できたか、課題は何かを探ってみようと思います。
※(2022/5/9追加)
開発しながら、Usecase
はdomain側に置くことで、domain側にビジネスロジックを寄せることができて理解しやすいと思いました。