見出し画像

モジュラーモノリス化するリファクタリングに投資しています

ナレッジワーク CTO の mayah (@mayahjp) です。

去年(2022年)後半ごろから開発人数が増えても生産性をキープするための施策の1つとしてモジュラーモノリス化を少しずつ進めていました。一定のマイルストーンとして大きな枠組みの合意と整理に達したため成果を共有します。

チームにあったアーキテクチャを求めてモジュラーモノリスに至りました

ナレッジワークは創業当初は数人のソフトウェアエンジニアで開発していました。初期 (2020年) はフロントエンドバックエンド含めても3人で、2022年頭でも正社員のソフトウェアエンジニアとしては6人でした。この頃はチーム分けも何もなく1チームで開発をしていました。

この頃は Go で書かれたサーバーサイドアプリケーションは単なるモノリシックなアーキテクチャでした。具体的には、ディレクトリ構造としては下の様にトップレベルで domainmodel や (grpc) service が分かれている構造でした。

domainmodel/
	tenant/
	user/
	group/
	knowledge/
	...
service/
	tenantservice/
	userservice/
	groupservice/
	knowledgeservice/
	...
infrastructure/
	rdb/
		user/...
		knowledge/...

初期にこの構成を取ったのにはいくつか理由があります。一番大きな理由は、コードを愚直に読めばわかるということを重視していたことです。大きな仕様変更を何度も繰り返していたため、規模が小さいうちはフラットな構造にしておく方が全体を把握がしやすく開発速度も上がりやすいと考えていました。きちんとモジュールを作るよりもパーツをうまく分けつつある程度ゆるめの構成としておいたほうがコードの組み替えがしやすく、開発速度に貢献していました。

このゆるめの構成は、ずっと続けるわけにはいきません。2022 年中頃には事業も軌道に乗ってきており、事業計画上はソフトウェアエンジニアは大きく増員することが想定されます。2022 年後半には1チームで開発することはままならなくなり、2023年初め頃には必ずチーム分けをしなければならないタイミングが訪れます。

初期のソフトウェアの構造は1チームで開発することに適した構造であって、複数チームでの開発にはあまり適した形ではありません。そのため、必ず何らかのミスマッチが訪れることが目に見えています。また、2年も開発を続けていくとモデルもかなり安定してきており、そろそろきちんとしたモデル化、モジュール化 (ここでは Go のモジュールの意味ではなく、意味のある単位でパッケージとして切り出すことをさしています) が可能なタイミングでもあります(実際にはモデルそのものはもっと前からある程度作っておりました)。

そこで、本格的なチーム分けが訪れる前にある程度モジュールを整備し、複数チームで開発可能な体制にしていくことにしました。ここで、ソフトウェアアーキテクチャは開発組織と相似形となるというコンウェイの法則が想起されます。コンウェイの法則を意識し、将来的な開発組織を想定しながら、2022年の後半を使って少しずつ意味の単位でのモジュール化をしていくことにしました。

モジュラーモノリス化を進めるのに必要なのは最終系をあらかじめ想定しておくこと、そして最後は腕力です

一般的には、先ほど述べたようなモノリシックなアーキテクチャはモデルが複雑に絡みあった「大きな泥団子」と呼ばれるスパゲティ化を起こしやすくなり、意味の単位でモジュールを切り出すことがいつのまにか非常に難しくなってしまうことが多いです。しかし、少なくとも自分は将来的にはモジュール化するということを念頭におきながらコードを書いていました。多少のスパゲティ化は起きていたものの、実際に作業するとなったときでも一部をのぞいて泥団子化は起きておらず、たいした問題にはなっていませんでした。ここはある程度最終系を想定しながら作れていたために大きな問題にはならなかったと考えています。

モジュラーモノリス化への準備として、さきほど述べた初期のアーキテクチャのディレクトリ構造を、以下のような構成に変えていきます。

tenantmodule/
	tenantmodule.go
	model/
	internal/
		infrastructure/
		adapter/
		usecase/
usermodule/
	usermodule.go
	model/
	internal/
		infrastructure/
		adapter/
		usecase/
knowledgemodule/
	knowledgemodule.go
	model/
	internal/
		infrastructure/
		adapter/
		usecase/

トップの構造を、意味の単位でモジュールを切っています (何度も言いますが go の module ではなくモジュラーモノリスの文脈でのモジュールです)。理想的には、各モジュールはモジュール API としていくつかの操作・参照系のインタフェース群および、参照用のデータ構造を公開するようにします。モジュール内の操作は internal package 以下に隠蔽されており、API を通してのみアクセスできる形になります。

参照用のデータ構造は model というパッケージの下に置かれていますが、これは残念ながらいわゆる「モデル」という言葉から想定されるものとは違います。他の module に公開しているオブジェクトたちが含まれており基本的には参照専用となります。モデルではなく DTO (data transfer object) と呼ぶのがおそらく適切なのですが、過去このレベルのものをモデルと呼んでいたので今でも model というディレクトリの下においています。どこかで変えるかもしれません。

現実にはここまで一気に変えるのは非常に時間がかかるため、少しずつ移行作業を行っていきます。

まず揃えたのはパッケージ構造で、上に書かれたパッケージ構造への移行は完了しています。唯一大変だったのが user 周りや group 周りのコードです。ユーザー取得まわりのコードで layer violation がいくつか起きてしまっておりこれを紐解いて整理するのに2週間ほどかかりました。ここだけは泥団子っぽくなっていっていました。ただユニットテストもまあまあ整備されていたため、腕力でなんとかしました。

更新を全て更新用 API を経由する、というのはまだ一部のパッケージだけにとどまっています。更新用の実装はデータ構造へのアクセス含めて全て internal に隠蔽するところまでは出来ているので、過渡的に一部本来モジュールの外に公開してはならない API を公開することで急場を凌いでいます。こちらは徐々に API を整備することで隠蔽を進めていくつもりです。

このような大規模なリファクタリングは最後は腕力がものをいいます。日々、プロダクトの機能開発だけしていてはアーキテクチャの構造上の問題が解決されることはありません。ナレッジワークでは必要に応じてこのような腕力でリファクタリングを進めていくことも行われていますが、そのための人的投資および時間的投資をきちんと行っています。例えば、プロダクト開発ロードマップとは異なるより良い開発のためのエンジニアリングロードマップを策定しリファクタリングの時間を確保する仕組みがあったり、プロダクト開発にばかり時間を使ってしまった場合はリファクタリングスプリントを設けてリファクタリングを進めるための時間を取れるようにしています。

リファクタリングを一緒に進めてくれる方を募集しております

単なるモノリスであったアーキテクチャをモジュラーモノリス化する取り組みを紹介しました。まだ過渡期であるため実装を完全にモジュールに閉じ込めることは出来ていないのですが、大きな枠組みまでは整備できています。あとは実装するたびに少しずつリファクタリングを重ねていくことになるでしょう。

ナレッジワークではリファクタリングを一緒に進めてくれる方を募集しております。

まずは話を聞いてみたいという方も大歓迎ですので、その場合は下記カジュアル面談フォームよりご応募ください!


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!