Server Actions で経費削減できた話
はじめに
個人開発にて、App Router
のフロントエンドとkotlin / Spring Boot / REST API
のサーバーサイドで構築されるシステムを2022年くらいから運用していました。機能的には問題なく使えていたのですが、如何せんAWS / EC2
上にNginx
, Tomcat
, RDB
を立てているため、初年度無料枠で使用できても2年目からは月々2000~3000円ほど利用料が発生していました。
どうにかいい方法はないかと模索していたら、Server Actions
が使えるのでは?ということで思い切ってサーバーサイドを刷新することにしました。
以下は、この刷新を受けて得られたメリット・デメリットです。
メリット
- 今回採用した環境ではすべて無料なので、サーバー使用量がまるッと浮いた。(2024/08/13時点)
- kotlin のお守りがなくなり、TypeScriptのみの改修が可能になった。
- REST API / サーバーサイドを立ち上げる手間がなくなった。
デメリット
-
App Router
がサーバーサイドのコードを管理しないといけなくなり、ディレクトリ設計どうしようか考え中(デメリットというほどではない) - 無料のRDBを使用しているので、若干遅く感じる(ちゃんと課金すれば、改善はされるはず)
- がんばって作った
kotlin / Spring Boot
の息の根を止めることになる😇
準備
サンプルコードはToDoアプリを題材にしたいと思います。
本質でないので、以下はサンプルコードに表現していないので、ご了承くださいませ
- スタイルの適用
- React.* などの自明なimport
- UIコンポーネントの実装(名称からなんとなく察してください)
'use client';
import { registerTodo } from '../server/registerTodo';
export const RegisterTodo = () => {
const [title, setTitle] = useState<string>('');
const [contents, setContents] = useState<string>('');
const registerHandler = async () => {
try {
await registerTodo({title, contents});
alert('登録しました。');
} catch(error) {
alert('失敗しました。');
}
}
return (
<div>
<TextInput
label={'タイトル'}
value={title}
setValue={setTitle}
/>
<TextInput
label={'内容'}
value={contents}
setValue={setContents}
multiple
/>
<Button
label={'登録'}
onClick={registerHandler}
/>
</div>
)
}
改修前の基本情報
当時私は、Reactを始めたばかりで、技術選定などしっかりできるほど知識はなかったので、なんとなくでaxios
使って、REST APIを叩きに行くって構成にしたんだと思います。axios
はREST APIを呼び出すことができるお手軽なライブラリです。
以下は、axios
を使った改修前のサンプルコードです。
import axios from "axios";
export const registerTodo = async (args: {
title: string,
contents: string,
}) => {
await axios.post(
'https://server/side/endpoint/api/v1/todo/register',
args
);
}
kotlin側のコードですが、よくあるMVCモデルで、Controllerで受けっとってRDB: CRUDするって感じのものです。
RDBとの疎通には、MyBatis
を使用しており、テーブルにCUDするものについては、Mybatis Generator
による自動生成コードを使用しておりましたが、Rについては、自作のSQLを書く必要がありました。この時点で単体テストの必要性があったり、手間がかかります。
サンプルコードを書いても消すことになるので、省略します。
余談ですが、REST APIの一式のファイル数は、自動生成含めてですが750以上ありました。一人で実装したわけですが、Server Actions
の登場により不要となりました😇 「あの時の対応時間、知見はなんだったんだ」という気持ちがありつつも、メンテコストが下がるというのはやはり気持ちが楽になりました。
改修後の期待
以下の要素技術を使います。
- Server Actions on Vercel(個人の範囲では無料)
- Apollo / hasura with GraphQL(個人の範囲では無料)
- PostgreSQL on NeonDB(個人の範囲では無料)
前後で対応付けすると次のようになります。厳密には完全に一致するものではないので、なんとなくのイメージを掴んでもらえればと思います。
要素 | 改修前 | 改修後 |
---|---|---|
サーバーサイド | REST API / Spring Boot | Server Actions on App Router |
RDSの実行環境 | MySQL on EC2 | PostgreSQL on NeonDB |
フロントエンドの実行環境 | Nginx on EC2 | Vercel |
サーバーサイドの実行環境 | Tomcat on EC2 | Vercel |
データ疎通するライブラリ | Mybatis with SQL | Apollo / hasura with GraphQL |
改修のアプローチ
Server Actions
を利用するまでに必要な環境の準備をしていきます。
hasura の準備
GraphQL APIを提供してくれるサービスです。以下のサイトから簡単に利用を開始することが可能です。
プロジェクトを立ち上げたら、GraphQLの元となるRDBを指定します。自前のRDSをお持ちであれば、そちらを指定することもできます。NeonDBであれば、無料で新規作成・使用することが可能です。
ここから、テーブルの追加もできるので、todo
テーブルを作成しておきましょう。
create table todo (
id uuid primary key,
title text not null,
contents text not null
);
hasuraの外からGraphQLを実行する場合は、権限が必要になるため、権限設定も忘れずに行っておきましょう
Apollo / codegen の準備
ちょうど、目的の記事があったので、引用させていただきます 🙏
今回は、ToDoを登録するものなので、以下のようなmutation
になると思います。
mutation insertTodo(
$id: String!
$title: String!
$contents: String!
) {
insertTodoOne(
object: {
id: $id
title: $title
contents: $contents
}
) {
__typename
id
}
}
これをcodegen
するのですが、その前に
codegen-client.yml
のpluginsにtyped-document-node
を適用するようにしておくと、冗長なコードがなくなったり、将来的な可用性が上がります。
codegen
すると、InsertTodoDocument
というファイルが生成されるかと思います。これはあとで使用します。
Vercel
Next.js
などのデプロイ・実行環境を提供するサービスです。GitHub
との連携もしているので、マージされたらVercelにデプロイってことも可能でなにかと便利です。
Server Actions
さて、本題です。
公式に倣って、実装すると
// 修正なし
+ 'use server';
- import axios from "axios";
+ import { getClient } from 'graphql/apollo';
+ import { InsertTodoDocument } from 'graphql/generate';
export const registerTodo = async (args: {
title: string,
contents: string,
}) => {
- await axios.post(
- 'https://server/side/endpoint/api/v1/todo/register',
- args
- );
+ await getClient().mutate({
+ mutation: InsertTodoDocument,
+ variables: {
+ id: // uuidを発行する
+ ...args,
+ }
+ })
}
REST APIのときと比較すると、革命的なコード量の減少です。一見増えているようですが、REST APIだとこの先、Controller
を実装して、ビジネスロジックを実装して、登録用のSQL書いてなどまだやることあります。どれだけ頑張ってもSpring Boot
で実装すると、100行は超えると思います。一方で、Server Actions
とGraphqQL
を使うことでこんなにも実装量を減らすことができました。
この例だと、Apollo
の関数を呼び出すだけでしたが、もう少し込み入った実装が必要な場面も出てくると思いますので、その際はファイルを分離したり、ディレクトリ設計をしっかりするがいいと思います。
まとめ
Server Actions
のサンプルコードを紹介しましたが、この登場は界隈でも批判的な意見出ているようですが、個人的にはありがたき産物なのかと感じています。
また、争点となっているクライアントサイドとサーバーサイドのコードがだいぶ近い場所に共存することになったので、それはそれでいい感じのアーキテクチャを考えるのもいいと思います。(気が向けば記事書きます)
Discussion