NestJSでモジュラモノリスを導入したお話し

## はじめに
初めまして、フロンティア株式会社のテックリードをしております「えばたん」です。
突然ですが、みなさん、NestJS使ってますか？
弊社は以前までバックエンドはRuby on Railsを利用していましたが、今年の頭にNestJS TypeScriptへ移行しました。
主な動機は
1. フロント・バックエンドともに同じ言語でかけるようにしたかった
1. 静的型付き言語を利用して開発体験を向上したかった
1. 今後複数のアプリケーションを開発したかった

一方、Ruby on RailsのActive Recordも非常に開発体験がよかったのですが、Prismaの台頭もあり、思い切って移行しました。

## NestJSを導入しようと思ったきっかけ
https://nestjs.com/

みなさんご存知NestJSですが、今回NestJSを入れようとしたきっかけが
1. monorepo機能がある
1. GraphQLをCodeFirstで書ける

の2つです。無論アーキテクチャモデルなども素晴らしいのですが、この素晴らしさはいろんな人が語っていると思うので省略します。

特に「1. monorepo機能がある」というのが弊社の状況からして一番よかったので、今回はこれを切り出して事例紹介をしたいなと思います。
（故にこの記事ではNestJSの基本やモジュラモノリスとは何かなどは省略しています。ご容赦ください。）

## なぜモジュラモノリスを採用しようとしている？
巷では、「マイクロサービスアーキテクチャ」の関心が強くなっています（もう下火なのかな...）が、個人的にマイクロサービスアーキテクチャは複数の部門の開発チームがあって、開発人数が多くいれば、有効な手法と見ています。

しかし、弊社のチームは実態の開発メンバーは社員1名、業務委託メンバーが5名の合計6名。
そんな中で、複数のアプリを開発するには、マイクロサービスアーキテクチャだと人も時間も足りない状況でした。
それに、実装しなければならない機能やアプリはあるため、どうしたものかと悩んでいました。


## NestJSのMonorepoモード
そこで着目したのが、NestJSでした。NestJSには「Monorepoモード」なるものがありました。

https://docs.nestjs.com/cli/monorepo

「うん！これこれ！」と言いたくなりそうなやつでした。
ひとつのリポジトリで複数アプリの起動わけができ、ライブラリとして共通機能の開発などもできるNestJSを採用しようとしました。

### やってみたMonorepoのメリット
1. 各アプリケーションごとにビジネスロジックを書けるので、開発者の興味関心をその1点に集中できる
1. マイクロサービスとは異なり、トランザクションを引き回すことができるので、エラーがどこかで発生した場合に全てロールバックができる
1. バックエンドのコードをひとつのリポジトリで管理するため、テストCIを回すのが楽

### やってみたMonorepoのデメリット
1. 開発環境の起動が重い、watchモードで起動すると複数のアプリがHot Reloadをしようとするためメモリめっちゃ食う
1. Build CIやテストCIが重い、そりゃ全部やってるから


## 実際に立ち上げてみる
```zsh
$ nest new sample-monorepo
⚡  We will scaffold your app in a few seconds..

? Which package manager would you ❤️  to use? npm
CREATE sample-monorepo/.eslintrc.js (663 bytes)
CREATE sample-monorepo/.prettierrc (51 bytes)
CREATE sample-monorepo/README.md (4369 bytes)
CREATE sample-monorepo/nest-cli.json (171 bytes)
CREATE sample-monorepo/package.json (1954 bytes)
CREATE sample-monorepo/tsconfig.build.json (97 bytes)
CREATE sample-monorepo/tsconfig.json (546 bytes)
CREATE sample-monorepo/src/app.controller.ts (274 bytes)
CREATE sample-monorepo/src/app.module.ts (249 bytes)
CREATE sample-monorepo/src/app.service.ts (142 bytes)
CREATE sample-monorepo/src/main.ts (208 bytes)
CREATE sample-monorepo/src/app.controller.spec.ts (617 bytes)
CREATE sample-monorepo/test/jest-e2e.json (183 bytes)
CREATE sample-monorepo/test/app.e2e-spec.ts (630 bytes)

✔ Installation in progress... ☕

🚀  Successfully created project sample-monorepo
👉  Get started with the following commands:

$ cd sample-monorepo
$ npm run start

                                         
                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.
                                         
                                         
               🍷  Donate: https://opencollective.com/nest

$ cd sample-monorepo
```
これでNestJSのアプリケーション作成は完了
次にmonorepoを有効にする
```zsh
% nest generate app app-2
DELETE src
DELETE test
CREATE apps/sample-monorepo/tsconfig.app.json (230 bytes)
CREATE apps/sample-monorepo/src/app.controller.spec.ts (617 bytes)
CREATE apps/sample-monorepo/src/app.controller.ts (274 bytes)
CREATE apps/sample-monorepo/src/app.module.ts (249 bytes)
CREATE apps/sample-monorepo/src/app.service.ts (142 bytes)
CREATE apps/sample-monorepo/src/main.ts (208 bytes)
CREATE apps/sample-monorepo/test/app.e2e-spec.ts (630 bytes)
CREATE apps/sample-monorepo/test/jest-e2e.json (183 bytes)
CREATE apps/app-2/tsconfig.app.json (220 bytes)
CREATE apps/app-2/src/main.ts (232 bytes)
CREATE apps/app-2/src/app-2.controller.spec.ts (632 bytes)
CREATE apps/app-2/src/app-2.controller.ts (281 bytes)
CREATE apps/app-2/src/app-2.module.ts (258 bytes)
CREATE apps/app-2/src/app-2.service.ts (143 bytes)
CREATE apps/app-2/test/jest-e2e.json (183 bytes)
CREATE apps/app-2/test/app.e2e-spec.ts (635 bytes)
UPDATE tsconfig.json (562 bytes)
UPDATE package.json (2039 bytes)
UPDATE nest-cli.json (856 bytes)
```
実行結果を見ると、srcを全て取っ払ってappsというところに元々あったやつと、新しく作った「app-2」というのを入れたっぽい
そして、元々のアプリを今のアプリケーション名にしているらしい。今回は内部的にはapp-1としたいので、
1. 名称の一括置換
1. `apps/sample-monorepo`を`apps/app-1`に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808763/27580c18-9dd9-aa18-8309-e7aec941488e.png)

あと、app-2を起動するためにpackage.jsonを修正
package.json(一部抜粋)
```json
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/apps/app-1/main",
+   "start:app2": "nest start",
+   "start:dev:app2": "nest start app-2 --watch",
+   "start:debug:app2": "nest start app-2 --debug --watch",
+   "start:prod:app2": "node dist/apps/app-2/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./apps/app-1/test/jest-e2e.json"
  }
```

起動時にアプリケーション名を指定すれば、そのアプリが開くとのことですので

最初に作ったアプリは
```zsh
$ npm run start:dev

> app-1@0.0.1 start:dev
> nest start --watch


 Info  Webpack is building your sources...

webpack 5.94.0 compiled successfully in 151 ms
Type-checking in progress...
[Nest] 16334  - 10/24/2024, 1:17:17 PM     LOG [NestFactory] Starting Nest application...
[Nest] 16334  - 10/24/2024, 1:17:17 PM     LOG [InstanceLoader] AppModule dependencies initialized +5ms
[Nest] 16334  - 10/24/2024, 1:17:17 PM     LOG [RoutesResolver] AppController {/}: +2ms
[Nest] 16334  - 10/24/2024, 1:17:17 PM     LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 16334  - 10/24/2024, 1:17:17 PM     LOG [NestApplication] Nest application successfully started +0ms
No errors found.

```

2つ目のアプリの起動は
```zsh
$ npm run start:dev:app2

> app-1@0.0.1 start:dev:app2
> nest start app-2 --watch


 Info  Webpack is building your sources...

webpack 5.94.0 compiled successfully in 157 ms
Type-checking in progress...
[Nest] 16298  - 10/24/2024, 1:16:27 PM     LOG [NestFactory] Starting Nest application...
[Nest] 16298  - 10/24/2024, 1:16:27 PM     LOG [InstanceLoader] App2Module dependencies initialized +5ms
[Nest] 16298  - 10/24/2024, 1:16:27 PM     LOG [RoutesResolver] App2Controller {/}: +1ms
[Nest] 16298  - 10/24/2024, 1:16:27 PM     LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 16298  - 10/24/2024, 1:16:27 PM     LOG [NestApplication] Nest application successfully started +2ms
No errors found.

```

これで起動できました！

## 共通モジュールの追加
AWSだとBucket、GCPだとCloud Storageの呼び出しとか、ORMの呼び出し、そのほか全てのアプリで共通利用したいものなどもNestJSの機能で解決できます。

https://docs.nestjs.com/cli/libraries

```zsh
$ nest g lib prisma
? What prefix would you like to use for the library (default: @app or 'defaultLibraryPrefix' setting value)? 
CREATE libs/prisma/tsconfig.lib.json (220 bytes)
CREATE libs/prisma/src/index.ts (67 bytes)
CREATE libs/prisma/src/prisma.module.ts (192 bytes)
CREATE libs/prisma/src/prisma.service.spec.ts (460 bytes)
CREATE libs/prisma/src/prisma.service.ts (90 bytes)
UPDATE nest-cli.json (1021 bytes)
UPDATE package.json (2321 bytes)
UPDATE tsconfig.json (686 bytes)
```
これで`libs/prisma`にモジュールの追加ができました。
あとはいつも通りサービスを書いたり、そのサービスを利用したい場合は、利用するモジュールの依存関係を注入すれば終わりです。

## 終わりに
開発メンバーの人数が少なく、規模としてもそれほど大きくない状態であればこの選択肢はありかなと感じました。
実際、弊社でもこのアーキテクチャを採用しましたが問題なく稼働できています。

最後に、弊社はエンジニアの採用をしております！もしご興味ございましたら以下リンクよりお話しだけでもしてみませんか？
今後様々なアプリケーションを0→1で開発できる他、新規事業にチャレンジできる環境もございます。
（リモートも相談できますし、利用したいパソコンも依頼いただければ貸与できます！）
ぜひご応募お待ちしております。

https://recruit.jobcan.jp/frontier/job_offers/1375165?hide_breadcrumb=false&hide_search=false