Header Logo
Ⓒ 2024- @itatibs
Clean Arcitectureのフォルダ構成を考える

Category: tech
Tags(仮): Python,CleanArchitecture,Python-CleanArchitecture
Date: 2021/8/15

はじめに

前回Clean Architectureでよく出てくる依存性逆転の原則で使われるDIコンテナをPythonで使う方法を確認しました。

次に、プロジェクトのフォルダ構成をClean Architectureに沿って検討してみようと思います。
とはいっても、自分は特にClean ArchitectureDDD(Domain Driven Design)に関する書物を読んだわけではなく、ググってこんな感じかな?といういわゆるオレオレClan Architectureです。
特に、実装クリーンアーキテクチャの内容を色濃く受けています。
数百ページの理論を学ぶより、実際に構築していく中で、やっぱり体で覚えていこうと思います。

色々やってみた系のブログを読むと、チーム内でも設計思想の違いから結構衝突が起きてるそうなので、どっちかというとコーディングルールのようにチームで体系だてられたものを作ってみんなで共通理解をするのが良い使い道そうです。

Clean Architectureを使う目的

まず、このアーキテクチャを使う目的を自分の中でしっかりと持っておきます。
そうでないと、作っていく中で自分の中でもブレが出てきて頓挫してしまいます。
また、目的に沿っていれば途中で構成を変えたいときも容易だと思います。

私はClean Architectureを主に以下の目的で使います。

  1. 機能ごとに分解してそれぞれの機能開発に注力したい
  2. それぞれの機能ごとにテストをしたい

特に自分はそれまでPythonでデータ収集のスクリプトなどを作成していたのですが、とにかく開発速度を要求されることが多く、機能としても  

  • データ元からデータを取得する
  • データを保存しやすいフォーマットに整形する
  • データベースに保存する

くらいしかなかったので、基本的に1ファイルの中の1つの関数で作成していました。
もちろんクラスなんてほぼ使わないです。クラスなんて前職の新人研修のJava研修以来ほぼ使ってない状況でした。

ただ、やっぱりそんなコーディングだと、動いているときはいいですが、後の仕様変更やバグ改修をするときに全ての構造を把握していないといけないので結構しんどい思いをしたのを覚えています。
もちろん自動テストできる状況ではなく、実際に動かしてみないとわからない状況だったので、リリースするときとかヒヤヒヤもんです。
なので、Clean Architectureを採用するときは、少なくともこれら2点が解決されていること目指します。

例えば、上記の例だと、3つの機能ごとにオブジェクトとして分解して、それぞれの機能でテストができることを目指します。

用語説明

フォルダ構成を検討するにあたって、この後出てくる用語について私が理解している範囲で説明します。
それぞれの用語の正式な意味については、ググった方が出てくるかと思いますが、難しい理論的なお言葉がつらつらと書かれていて中々理解できなかったので、たぶんこういう意味でいいよね?くらいのノリで解釈して扱っています。
ここらへんも、実際に作っていく中でそれぞれの用語の正しい理解ができていければいいなぁと思います。

Domain

DDDの最初のDのやつです。嘘です、そんなに詳しくないです。
ただ、Domainというものがビジネス側で定義する最も重要な定義みたいです。

Clean ArchitectureDDDを調べていると、よくビジネス側とか開発側みたいな言葉を目にします。
ビジネス側というのは、例えばソフトウェア開発については素人だけど、そのソフトウェアを使う分野に長けている人のことを指します。
銀行システムを作るとしたら、ビジネス側の人は倍返しが好きそうな銀行の行員さんです。

というのも、DDDの思想として、Domainは開発者側だけではなく、ビジネス側の人間にも理解できるように設計するというのがあります。
また、Clean ArchitectureだとEntityの外側にあるUseCasesまでがビジネスルールと定義しているので、ある程度そのソフトウェアの肝となる部分についてはついては普通の人が見ても分かりやすいように作ってあげる必要があります。  

で、このDomainってなんやねんというのがいまだに自分の中でも腑に落ちていないのです。
例えば、NPBの選手データを取得してデータベースに保存したいソフトウェアを構築するときは、Domain選手データがDomainにあたると思います。
そして、その選手データにある選手名はもちろん、所属チームやポジションといった情報の定義をここで記載しておきます。

また、この選手データは何ができるのかということも定義しておきます。
ここだと、データベースに保存できれば良いので、保存機能の定義だけしておきます。

このような選手データの情報についてをEntity、保存機能についてをRepositoryと今回は定義しました。
特に、Repositoryについてはデータを永続化(つまり保存)するケースについて用いる用語のようで、他には出力したいときにはPresenterというのを用いたりするようですが、ここらへんは詳しくないです。

Application

ここでは、名の通りこのソフトウェアは何ができるのかというのを記載します。
UseCasesで何ができそうなのかを抽象的に記載しておいて、Interactorでそれを具体化できるようにします。
ビジネス側の人はこのUseCasesで何ができるかを何となく把握できればよく、開発側の人は人知れずInteractorで中身を作るような関係です。

実際に開発していると、UseCasesで抽象化しておくことで、各機能と切り離せたり、DIコンテナを注入する隙を作れるようになると実感しました。

また、このInteractorで何かやるときにはだいたい何らかの入力(input)が必要でそれについては Inputdataとして別途定義しておきます。
ブログとか見ていると、このInputdataDTOみたいなものという記載があるので、最初は正直Entityとの違いが分からなかったのですが、InputdataはとにかくInteractorを動かすためにどういう入力が必要かに注力して考えるのが良いと思います。

Adapter

ここまでくるとビジネス側の人はあまり興味をもつ必要はなく、あとは開発側DomainApplicationをうまいこと動かすことができるように、ソフトウェア分野とDomainをAdaptさせてあげるような機能をゴリゴリ定義していきます。

例えば先に出てきたNPBの選手データを取得してデータベースに保存したいソフトウェアの場合は、Webからデータをとってくるとこや、データをデータベースに保存するところとなります。

この層のワードについてもあまり良く理解していないのですが、Inputになりそうな機能、その後Interactorに渡したい機能にはControllerに、データベースなどに関わる機能についてはGatewayに割り振ろうと思います。

特に、データベースからデータを取得してそれをInputとするような機能はControllerにすべきか、Gatewayにすべきかはまだ迷っていますが、Clean Architectureの図的には、Gatewayの方が近いのかな?というのが今の所の所感です。

Infrastracture

一番外側の機能になり、ここにフレームワークや外部ライブラリを定義すると良いと言われています。
正直まだここの使い所は掴めていないのですが、例えばGatewayでデータをPostgresに保存する機能を作った場合は、そのPostgresの接続情報やclientInfrastractureで定義しておくことで、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側にビジネスロジックを寄せることができて理解しやすいと思いました。