クリーンアーキテクチャをプロジェクトに実装した話

システム開発のプロジェクトにクリーンアーキテクチャ(オニオンアーキテクチャ)を実装しましたので、その具体的な方法を投稿させて頂きます。
記事中ぐらいに記載してます。前段は飛ばしてもらっても構いません。

ご興味がある方と何か情報共有ができればと思ってます。

##前段
私事ですが、少人数で開発する新規のWeb系業務システムのプロジェクトを立ち上げるにあたり、どのようなプロジェクト構成がよいか、経験も少なくよくわからなかったので、いろいろ考えてみたのですが、

以前に1度経験したMVCのWebアプリケーションは普通にやっていたので、モノリシックアプリケーションとなっていて、Model、Controller、Viewに分けていて、DB操作用のRepositoryも作成したけど、その役割とか、そのビジネスロジックをどうするのか微妙な部分があったり、Controllerがファットになったりで、うまくまとまらない感じがしていました。

システムのアーキテクチャとしてはどのようなものが良いか、インターネットで調べてみたところ、ドメイン駆動設計やクリーンアーキテクチャが重要だということがわかりました。

##ドメイン駆動設計とクリーンアーキテクチャって何？
Wikipediaより引用になりますが、ドメイン駆動設計とはソフトウェアの設計手法であり「複雑なドメインの設計は、モデルベースで行うべき」であり、また「大半のソフトウェアプロジェクトでは、システムを実装するための特定の技術ではなく、ドメインそのものとドメインのロジックに焦点を置くべき」であるとする、と書いてありました。

その中でドメイン駆動設計を実現するための1つの方法として、クリーンアーキテクチャというものが出てきます。

ということは、業務システム系のシステム設計をうまくやっていくにはドメイン駆動設計とクリーンアーキテクチャについて理解していくことがとても重要だと思いました。

本では「ドメイン駆動設計入門」を読んでみました。まだ理解しきれてないけど、この本で参考として使える部分はあると思います。ただし、この本では考え方が載っているけど、実装方法は自分で決めなければならないのはつらいところです。

<img width="100" alt="2dfbb60b-9423-e4dc-e21a-efcf8f215dbf.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216585/2dfbb60b-9423-e4dc-e21a-efcf8f215dbf.jpeg">

##プロジェクトにはオニオンアーキテクチャを採用することにした
今回、新規プロジェクトのため、クリーンアーキテクチャのうち、もっとも良さそうなオニオンアーキテクチャを採用しました。

ただし、そのクリーンアーキテクチャを実装するためには、ドメイン駆動設計の考え方の1つでもある、ドメイン(業務の知識)のエキスパートでもなければいけません。

なんとなく業務を理解してシステムを組み立てただけでは、業務を理解できてるとは言えなくて、システムっぽく動くだけですし。(それこそエンジニアの自己満足で終わってしまいます...)

##システム開発でよく見かけるもの(やばいやつ…)
クリーンアーキテクチャの具体的な話しを始める前に、

他のエンジニアの書いたコードってわからない、ってよく思うのですが、私はこのようなものをよく見かけて、今後はなんとかしたいなって思ったのもクリーンアーキテクチャを始める理由の1つでした。
すでに動いてるものは触らないけどね…

(良くないケース)
・業務系システムはフォーム系アプリ一択
・そもそもMVC系、レイヤー系アーキテクチャからは程遠い…
・フォーム系アプリを作る人の特徴として、1つの機能でプロジェクトにして、それをプロジェクト単位のマイクロサービス化。プロジェクト名はCS001、CS002とか書いてて、20～30個ぐらい作ってた。(それってただの1機能じゃん...)
・プロジェクト内でフォルダ分けもしてないし、フォーム系アプリであるある、画面のコードにビジネスロジックをダラダラっと書いてた。
そしてそこから直接DBアクセス…
・画面にビジネスロジックがあるから、やってることがほぼ関数型プログラミング中心(VBAでよく使われる作り方と同じじゃん...)
・フォーム系アプリでもレイヤー系アーキテクチャ使えるでしょ…
・ORMつかわず、SQL中心で書いてた
・ORM使ってないから当然のごとく、日本語名テーブル
・画面名はfrm001とか書いてた
・そもそもシステムの画面デザインが汚い
・そもそも画面デザインが汚いから、ユーザービリティなんてない
・入力検証をしていなかった。
・入力検証してたとしても、Webアプリではクライアント側だけでやっていた...
・ローカル変数を不必要に羅列
・メソッド構成とか共通メソッドとか意味不明、よくわからない引数の使い方とか
・同じクラス内で共通メソッド作って、それを使うなよ...　しかも共通化しなくていいことを共通化していた。　(制御系ならあるかもしれないが...)
・DIなんて無縁
・上記の様々な理由からオブジェクト指向プログラミングは破綻…
・エンジニアは仕様が全てで業務のことをよく知らないらしい…
・エンジニアのくせにシステムの仕様がどうのとか、変更は困るとかうるさい(お前はユーザーじゃないだろ...)
・最低限のシステムセキュリティについて無知

とか、あげれば色々ありすぎるけど。スパゲティコードも多いし。

そもそも、他のエンジニアから見て読みにくいコードはNGだと思います。

システム開発は自由度がありすぎて、いろいろなやり方があるので、個人の考え方に偏り過ぎてしまいがちで、いろいろなやり方ができてしまい、闇にハマってしまいますね。

プログラムをきれいに整理したほうが効率的だし、システムに関わる人には優しいんですけどね。



##オニオンアーキテクチャの図としてはこれを参考にしました
<img width="200" alt="4067ab41-de92-50da-73ce-61fa12a273c0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216585/4067ab41-de92-50da-73ce-61fa12a273c0.png">

話は脱線してしまいましたが、これはよくネット記事で見かけるオニオンアーキテクチャの図です。

オニオンアーキテクチャで他のタイプもありましたが、実際のプロジェクトに実装するにはこの6つのレイヤーに分かれているのが使いやすそうです。そして、このようにサークルの図になっているだけだと、実際の実装はどうなるのかは、パッと見はわからないです。

##オニオンアーキテクチャを図形と矢印の表で表現してみた
そこで、オニオンアーキテクチャを別表にしてまとめた記事を見つけ、それをもとに実装してみました。
その記事は、間違いではなかったのですが、作ったものが若干異なっていたので、表の見直し(解釈方法の変更)をし、以下の図にまとめました。

今回紹介しているクリーンアーキテクチャはWebシステムがベースです。イントラネットのシステムでもなるべくWebシステムベースにしたほうが開発にかかるコストは低いと思います。

あくまで生産管理系での話になりますが、
私はメインの管理系システムはWebシステムと相性がよく、制御系などデスクトップアプリが必要な場合のみをデスクトップアプリにするべきだと考えています。
(近年はデスクトップアプリでしかできなかったことが、Webアプリでもできるようになってきている。Webアプリは進化しているが、デスクトップアプリは停滞気味。)

(具体例)
<img width="800" alt="ac3a2688-6ef2-5f66-2dce-233893caefce.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216585/ac3a2688-6ef2-5f66-2dce-233893caefce.png">

また、アプリケーションを組む時は、画面に関係する各クラスの基本メソッドについて下記とした。
・UserInterfaceのController : CRUD + Sort または　R + Register
・ApplicationServiceのService : CRUD + Sort  または　R + Register
・DomainSeviceのRepository : CRUD + Sort
のように、CRUDタイプのメソッドを基本とするようにしました。(CRUD : GetAll, GetById, Create, Update, Delete)

私はクライアント側ではvue.js3とag-gridをベースに使用してます。
npmは使用してますが、Nodeプロジェクトではありません。

SortはGridのデータの並び替えをする時に利用してます。
R + Registerについては、 RはここではGetAllだけのこと。RegisterはGrid直接編集画面でデータをサーバー側へ一括送信するときに利用してます。

クラスのメソッドは基本的にCRUD方式で作ったほうがすっきりします。メソッドを自由にしたら、それは他人にはわかりにくいものになってしまいますし。

人にわからないコードは書かないようにしたいですね。

##補足
・クライアント側の工夫としては、先ほど記載したとおりvue.js3を導入して、1画面につき1つの画面専用のjavascriptとしてます。
gridを非同期で更新できれば、わざわざ画面全体をSingle page applicationにする必要もないかなと個人的には思います。

・クライアント側の各画面ごとのメソッド名はある程度揃えてます。

・Reactの利用も考えましたが難しすぎたので、Vue.js3を採用しました。
今回は画面も製造していますが、画面をデザイナーとか新人に任せることになったらReactはたぶん無理...

・jQueryは利用していません。jQueryを使うとjavascirptが複雑になります。ES6のjavascriptとVue.jsの利用により、不要と判断しました。

・サーバー側とクライアント側のvalidationを一緒にできない場合は分けてもいいかも。
(クライアント側で基本はじく、サーバー側で2重チェックだよね)

・システムの画面デザインはBootstrap5を使ってきれいに整えましょう。

・InMemoryフォルダはDB操作以外のメールサービスなどの外部サービスに該当します。

・UserInterface->DomainServiceの直接の利用はユーザーチェックの時のみとしてます。
・例外の場合はあり。

・ごめんなさい、typescriptで型指定がいいのはわかるのですが、私はtypescriptのことをよくわかっていない...


##オニオンアーキテクチャにDIは必須
オニオンアーキテクチャにDIの利用は必須でした。最初、DIって本当に意味不明だったのですが、

わかりやすく言うと、具体例の通り、DIで依存関係を逆転させています。DIにクラスとインターフェースを登録すると、2つを紐付けしてくれます。コンストラクタ引数に当てると、DIコンテナがnewしたものを渡してくれます。

私の環境ではコンストラクタ注入というものでした。
なので、コンストラクタ引数にインターフェース型として指定できるし、使う時はインターフェースのメソッドとして使えます。

##システムは技術とドメインの知識の両方があってできるもの
後半、技術的なお話ばかりになってしまいましたが、技術も重要ですが、技術だけに固執せず、業務のドメインに関わる知識を深めていただいたうえで、どうすればドメインとドメインのロジックがうまくいくのかを考え、そこで技術を有効活用して、システム開発を進めていただければと思います。


何かアドバイスございましたら、よろしくお願いします。