Lambdaで使ってるNode.jsを18.xにアップデートした話 a.k.a 技術的負債の解消

# Node.js 14.xから18.xへのバージョンアップと周辺技術の更新

ソフトウェアの進化は早く、定期的なアップデートは避けて通れません。

https://qiita.com/autotaker1984/items/a3091772dbb0fb91473d

このたび、サービスで利用しているLambdaのランタイムにおいて、Node.jsをバージョン14.xから18.xへとアップデートしました。

このことに伴い、多くの周辺ライブラリやツールもアップデートまたは置き換えを行いました。
本記事では、その過程で直面した障壁とそれを乗り越えた経験を重点的に紹介します。

## アップデートの背景

大きな理由としてNode.jsの14.xバージョンがサポート終了を迎え、AWSのLambdaでも14.xがDeprecatedとなったことから、アップデートを決断しました。

Node.jsの16.xも検討しましたが、すでに対応した当時であってもサポート終了が2024年6月12日と間近に迫っていため、18.xにアップデートしました。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

(タイトルで技術的負債の解消などど銘打っていますが、必要に迫られてアップデートしたというのが本音です。)

## 対象となったサービスで使われている代表的な技術

- インフラ:
    - Serverless Framework
        - Lambdaを代表する各種AWSリソースを作成
- CI/CD:
    - AWS CodeBuild
        - ビルドだけでなく、テストやデプロイにも使用
- バックエンド:
    - Node.js
        - 今回のきっかけとなったLambdaのランタイム
    - TypeScript
- フロントエンド:
    - TypeScript
    - React
- test:
    - Jest
        - 単体テスト
    - Cypress
        - E2Eテスト
    - Human Power
        - 要するに人力

## 主なアップデート対象

- **Node.js:**
    - 14.x -> 18.x
    - LambdaのランタイムバージョンがDeprecatedとなったためです
- **Serverless Framework:** 
    - 1.x -> 3.x
    - これはNode.jsのバージョンアップに伴い必要となりました
    - また、関連する複数のプラグインも更新や置き換えを行いました
- **@types/node:** 
    - 14.x -> 18.x
    - Node.js18に合わせて行いました
- **TypeScript:** 
    - 3.x -> 4.x
    - @types/nodeと同様にNode.js18に合わせて行いました
- **Amazon Corretto:** 
    - 8.x -> 17.x
    - Node.js18へのアップデートに合わせて、ローカルの開発環境で使っていたものを更新しました
- **Cypress:** 
    - 5.x -> 10.x
    - これもNode.js18へのアップデートに伴うものです

## アップデートのプロセス

アップデート過程での主なステップは以下の通りです。

1. **依存関係の確認:** 
    - 現在利用しているライブラリとツールの中で、新しいNode.jsバージョンと互換性のあるものを洗い出しました
    - 実際には一つライブラリのアップデートを行うたびにビルドや自動テストを回していきながら、都度エラーが出たものを修正・更新していきましたが、事前にある程度確認しておくことで、手戻りを減らすことができました
1. **ライブラリの更新:** 
    - ローカルの開発環境に新しいバージョンのNode.jsをインストールして、ライブラリを更新しました
    - また、CI/CDも新しいバージョンに対応させるため、CodeBuildの設定を変更しました
1. **コードの更新:** 
    - ビルドや自動テストを回して、エラーが出た箇所を都度修正しました
1. **テストの実施:** 
    - 単体テスト、E2Eテスト等の自動テストはCI/CDに組み込まれているので、AWS上の開発環境へデプロイするたびに自動で実行されていますが、大きな修正になるので、手動テストもかなり綿密に行いました
1. **段階的なデプロイ:** 
    - ステージング環境での適用テストを経て、最後に本番環境へリリースしました

## 遭遇した問題と解決策

障壁となった最大の課題は、既存のコードと新しいNode.jsバージョンとの間に生じた互換性の問題です。具体的には以下のような解決策を講じました。

- 一部のライブラリが新バージョンに対応していなかったため、互換性のある代替ライブラリを探し、必要に応じて独自に修正を加えました

### Serverless Frameworkのアップデート

Serverless Frameworkは、Node.jsのバージョンアップに伴い、1.xから3.xへのアップデートが必要でした。

https://www.appliot.co.jp/blog/aws-lambdanode-js-v18-serverless-framework/

https://blog.open.tokyo.jp/2022/12/30/migrate-aws-lambda-node-js-14-to-18.html

かなり放置されていたツケが回って、Serverless Frameworkのバージョンアップに伴い、様々なプラグインも更新や置き換えを行う必要がありました。

#### 各種プラグイン

Serverless Frameworkではプラグインを利用して、様々な機能を追加することができます。
ただし、プラグインもServerless Frameworkのバージョンに依存しているため、今回、様々なプラグインも更新や置き換えを行う必要がありました。

そんな中、以下二つのプラグインはメンテナンスされていなかったので置き換えではなく削除することになりました。

- serverless-aws-documentation
    - Serverless Framework自身がAmazon API Gateway のドキュメントとモデルに対応したため、不要になりました
    - ただしこれによって作成されていたSwagger UIのドキュメントは、イチから作りなおす必要がありました
- serverless-plugin-tracing
    - Serverless Framework自身がTracingに対応したため、不要になりました

### TypeScriptのアップデート

TypeScriptは、Node.js18に合わせて、3.xから4.xへのアップデートを行いました。
これはまず、

```
npm dist-tag @types/node
```

を実行すると、

```
ts4.2: 18.15.3
```

となります。

Node.jsを18使うので、`@types/node`も18に更新して、
それと対応しているTypeScriptも4.2にまで上げるというのが常道だと考えたからです。

本当は修正量が大きくなるため一度にTypeScriptのVersionまで上げるのは避けたかったのです。
ただ別々にアップデートするのだとビルドエラーが出たため、一気に上げることにしました。

### Cypressのアップデート

https://docs.cypress.io/guides/getting-started/installing-cypress#Operating-System

Cypressは、Node.js18に対応するため、5.xから10.xへのアップデートを行いました。

※Node.js14のままでもCypress10.xは動作したため、先にCypressのアップデートを行い、その後Node.jsのアップデートを行いました。

というのもCypressがv10にアップデートされるとファイルの拡張子が変更されるなど、修正量が大きくなったからです。
ただでさえ修正量の大きいNode.jsのアップデートと同時に行うと、レビュワーの負担が大きくなることや、バグの発生リスクが高まるので、それらを回避する意図がありました。

Cypressのv10へのアップデートには、以下の記事を参考にしました。

https://qiita.com/odennkunn/items/bd643f5aa1cd0e6930f0

この記事でも紹介されていますが、マイグレーション作業は自動化されていたため、比較的スムーズに進めることができました。

### Amazon Correttoのアップデート

Amazon Correttoは、Node.js18に合わせて、8.xから17.xへのアップデートを行いました。

これはCodeBuildでのビルド時に使用しているため、Node.js18に合わせて行いました。

というのも、CodeBuildのランタイムは以下から参照することができますが、

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/available-runtimes.html

Node.js18に対応しているランタイムは、Ubuntu standard:7.0であり、
そのランタイムにはAmazon Corretto 17が使用できるからです。

## 今回対応しなかったこと

今回は、Node.jsのバージョンアップに伴うライブラリやツールのアップデートを行いましたが、以下については今回対応しませんでした。

- aws-sdk v2 -> v3のアップデート
    - これはNode.js18において、AWS SDK for JavaScript v3が推奨されているため、アップデートを検討していましたが、アプリケーションレイヤーの修正量があまりに大きくなるため、今回は見送りました
    - 以下の記事やAWSドキュメントを参照して、今後対応する予定です

https://blog.open.tokyo.jp/2023/01/04/migrate-aws-sdk-node-js-v2-to-v3.html

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v3/developer-guide/migrating.html

# まとめ

非常に多くライブラリ/ツールのアップデートを行いましたが、段階的に進めることで、問題を最小限に抑えることができました。(それでも個人的には死ぬほど大変でした)

※Node.jsとServerless Frameworkのバージョンアップは、修正量・影響度どちらを鑑みても別々に行うべきかと思います。
しかしながらこれはServerless FrameworkのバージョンアップがNode.jsのバージョンアップに依存していたため、一緒に行う羽目になりました。

また本来であれば様々なライブラリの更新は常日頃から行っていくべきですが、実際には他の業務や修正に手一杯で、なかなか大変です。

ただ、今回のように大きな修正をするよりも、小さな修正を定期的に行う方が、コストやリスクを抑えることができるということを実感しました。

今後はRenovateなどのツールを活用して、定期的な更新を行っていきたいと考えています。