microCMS の定義ファイルから SDK を生成する microcms_sdk_generator を作った
ありがたいことに CMS を利用した案件が続きそうなので、よく使いそうなヘッドレス CMS である microCMS の型安全な SDK を生成する microcms_sdk_generator を作りました。
microCMS とは
日本製のヘッドレス CMS です。
ヘッドレスなので WordPress のように閲覧側の UI は提供せず、入稿したデータを API 経由で得ることができます。
取得したいデータを API として定義すると入稿用の UI を利用することができます。スキーマ定義は技術者が行い、入稿を依頼元の担当者に行ってもらう、という使い方をすることが多いです。
microcms_sdk_generator の目的
microCMS を利用した開発では作成した API からデータを取得し、表示する実装がメインとなります。
今のところは Web サイトなどの構築で使うのが主なので、TypeScript 系で開発することが多いです。
View 以外全般を担当することが多い私の仕事では、API のスキーマ定義をし、定義を元に API Client を整備し、View の開発者がデータを扱えるようにするのが主な作業となります。
microCMS では公式の SDK も提供されているのですが、公式の SDK では API の定義によらない汎用的な実装を提供しているため、型安全に使用するには各々で型定義をメンテナンスする必要があります。
型定義のメンテナンスでは API からのレスポンスが期待している形式かどうかをデバッグする必要もあります。スキーマ定義は開発中に変更することも多く、型定義の間違いに早い段階で気づくために Zod のようなスキーマ定義ツールを導入しバリデーションと型保証を行うことも多いです。
これらの型やスキーマ定義のメンテナンスは API が増えてくるとそれなりに時間的なコストを費やすことになるのですが、そもそもスキーマ定義間のマッピングは機械的に解釈できるものであり、人間がやる必要はないのでツールによる自動化を図りました。
生成する SDK では API Client の実装と Zod のスキーマ定義や型を提供し、parse した状態で Response を返します。
これで「JSON 型付け係」から脱却できます。
microcms_sdk_generator の使い方
最新情報は README.md を参照してください。
インストール
Deno と dnt で作成したので、Deno でも npm などでも利用できます。
deno install --allow-read --allow-write https://deno.land/x/microcms_sdk_generator/microcms_sdk_generator.ts
npm install --global microcms_sdk_generator
また、直接実行することもできます。
deno run --allow-read --allow-write https://deno.land/x/microcms_sdk_generator/microcms_sdk_generator.ts
npx microcms_sdk_generator
実行
以下のように実行します。
microcms_sdk_generator <schema directory> <destination typescript file>
schema directory
は以下のような構成にしてください。
<schema directory>/
- list/
- endpointName1.json
- endpointName2.json
- …
- object/
- endpointName1.json
- endpointName2.json
- …
- list/
各 endpoint のスキーマ定義は以下のドキュメントを参照し、エクスポートしてください。
schema directory
の構成を指定しているのは、上記でエクスポートしたスキーマ定義の JSON ファイルでは endpointName
と API type (list or object)
が得られないため、判別するのにディレクトリ構造を利用している事情です。(API 単位のエクスポートを機能要望中)
生成された SDK の使い方
初期化は以下のように行います。公式 SDK とほぼ同じです。
const client = createClient({
serviceDomain: "YOUR_SERVICE_DOMAIN",
apiKey: "YOUR_API_KEY",
});
主なリクエストは以下のようになっています。引数の仕様は型を参照してください。
const resp = await client[`${endpointName}`].list({...})
const resp = await client[`${endpointName}`].get({...})
const resp = await client[`${endpointName}`].post({...})
const resp = await client[`${endpointName}`].put({...})
const resp = await client[`${endpointName}`].patch({...})
const resp = await client[`${endpointName}`].delete({...})
const resp = await client[`${endpointName}`].listMetadata({...})
const resp = await client[`${endpointName}`].getMetadata({...})
const resp = await client[`${endpointName}`].patchStatus({...})
以下は例です。
const listResp = await client.sampleForListApi.list({
limit: 2,
orders: ["-createdAt"],
});
if (!listResp.ok) {
throw listResp;
}
console.log(listResp.data.contents);
以下のテストファイルを見ると雰囲気がわかるかと思います。
microcms_sdk_generator/src/testdata/crud.test.ts at main · hori-ryota/microcms_sdk_generator
また、初期化およびリクエスト時には option として RequestInit
などの上書きをできるようにしています。 fetch
の実装も customFetcher
として上書きできるようにしているので、用途に合わせて柔軟に対応できるはずです。
(余談)Client のインターフェースの思想について
Client のインターフェースとして以下の 2 パターンを検討しました。
client.${endpointName}.list()
client.list${endpointName}()
以下の理由で前者にしています。
- 利用時に「〜〜を〜〜したい」「〜〜したい、対象は〜〜」だと前者で思考することが多かったので、対象 endpoint をまず決めてから操作を後置する形にした
- endpoint が先に来たほうが用途に応じた Client の部分集合も作りやすいと感じた
後に違ったな、と感じたら調整するかもですが、いったんは良さそうに感じています。
(余談)Deno と dnt によるツール開発
Next.js のように標準の Eslint 設定などがあればいいのですが、シンプルなツールを開発するために TypeScript の開発環境として各種ツールの設定を整備するのがつらいと感じてしまいました。
Web サイトや Web アプリケーション系のチーム開発なら再利用可能な知見も多く整備する意義(意欲)もありますが、シンプルなツールを開発する場合は実装やテストコードより設定ファイルが多くなりがちでちょっとつらいなと。devDependencies も増えるので把握することが多い。
dnt を使えば Deno で開発して npm で提供できるので、今回は formatter や linter、tester などが組み込みの Deno で開発してみました。体験めっちゃ良かったので、今後も似たようなツールは Deno で作ろうと思います。Go とどっち優先するか迷うレベル。
TypeScriptで色々作ることも増えたけど、やっぱlintとかjestとか設定するものが多くてつらい。
— hori (@hori_ryota) July 13, 2023
microCMSのsdkを生成するツールを作ろうと思ったけど、Denoにしちゃうか…?
以上
以上、 microcms_sdk_generator の紹介でした。まだ作ったばっかりなので、各案件で利用してみて改善を重ねていきます。
実際の案件では SDK をそのまま View レイヤーに露出せず用途にあった型に詰め替えたりすることも多いですが、シンプルな構成ならデータ層は生成した SDK で完結できそうです。
バックエンド特化のエンジニアとしては本領のインフラや各種フローの構築に集中できるので、とても捗りそうです。