😽
プログラミングのようにAPI仕様を定義するアイディア共有
プログラミングのようにAPI仕様を定義するアイディア共有
アイディア共有記事です。
従来の DBスキーマ形式 や JSON/YAML形式 ではなく、プログラミング言語のように定義することで
- 動的な構造やロジックが自然に表現できる。
- 冗長性が排除され、可読性が向上する。
- 状態遷移や条件分岐を直接記述できる。
アプローチの設計方針
-
プログラミング言語風のAPIスキーマ構文を設計する。
- 関数のようにエンドポイントを定義。
- 条件分岐やレスポンスを直接記述可能にする。
-
DSL (Domain Specific Language) の設計:
- API仕様に特化した軽量な構文を設計する。
- JavaScriptやPython風の構文をベースにすることで直感的な理解を目指す。
-
動的生成スクリプトの構築:
- DSLをパースして、設定ファイルやテンプレートを動的に生成する。
プログラミング風のAPIスキーマ構文設計
DSLの基本構文:API仕様書を関数のように定義する構文例:
endpoint("/users/:userId", "GET") {
params {
userId: int
}
query {
status: enum("active", "inactive", "suspended")
}
response {
userId: int,
status: string,
lastLogin: datetime,
role: enum("admin", "user", "guest")
}
logic {
if (query.status == "active") {
return {
status: "active",
lastLogin: now(),
role: "user"
};
}
if (query.status == "inactive") {
return {
status: "inactive",
lastLogin: null,
role: "guest"
};
}
return {
status: "suspended",
lastLogin: null,
role: "guest"
};
}
}
DSLの構文解析とコード生成
DSLパーサの設計:
DSLの構文を解析して、設定ファイルやテンプレートを生成する。
dsl-parser.js
const fs = require("fs");
const sampleDSL = `
endpoint("/users/:userId", "GET") {
params {
userId: int
}
query {
status: enum("active", "inactive", "suspended")
}
response {
userId: int,
status: string,
lastLogin: datetime,
role: enum("admin", "user", "guest")
}
logic {
if (query.status == "active") {
return {
status: "active",
lastLogin: now(),
role: "user"
};
}
if (query.status == "inactive") {
return {
status: "inactive",
lastLogin: null,
role: "guest"
};
}
return {
status: "suspended",
lastLogin: null,
role: "guest"
};
}
}
`;
function parseDSL(dsl) {
const endpointPattern = /endpoint\("(.+?)",\s*"(GET|POST|PUT|DELETE)"\)\s*\{([\s\S]+?)\}/g;
const paramPattern = /params\s*\{([\s\S]+?)\}/;
const queryPattern = /query\s*\{([\s\S]+?)\}/;
const responsePattern = /response\s*\{([\s\S]+?)\}/;
const logicPattern = /logic\s*\{([\s\S]+?)\}/;
const endpoints = [];
let match;
while ((match = endpointPattern.exec(dsl)) !== null) {
const [_, path, method, body] = match;
const paramsMatch = paramPattern.exec(body);
const queryMatch = queryPattern.exec(body);
const responseMatch = responsePattern.exec(body);
const logicMatch = logicPattern.exec(body);
endpoints.push({
path,
method,
params: paramsMatch ? paramsMatch[1].trim() : "",
query: queryMatch ? queryMatch[1].trim() : "",
response: responseMatch ? responseMatch[1].trim() : "",
logic: logicMatch ? logicMatch[1].trim() : ""
});
}
return { endpoints };
}
function generateConfig(dsl) {
const { endpoints } = parseDSL(dsl);
const config = { endpoints: [] };
endpoints.forEach(endpoint => {
const { path, method, logic } = endpoint;
const responses = [];
const logicLines = logic.split("\n").map(line => line.trim()).filter(Boolean);
logicLines.forEach(line => {
if (line.startsWith("if")) {
const condition = line.match(/if\s*\((.+?)\)\s*\{/)[1];
const response = line.match(/return\s*\{([\s\S]+?)\};/)[1];
responses.push({ condition, response: JSON.parse(`{${response}}`) });
}
});
config.endpoints.push({ path, method, responses });
});
fs.writeFileSync("config.json", JSON.stringify(config, null, 2));
}
generateConfig(sampleDSL);
``
### 生成される設定ファイルの例
config.json:
```json
{
"endpoints": [
{
"path": "/users/:userId",
"method": "GET",
"responses": [
{
"condition": "query.status == 'active'",
"response": {
"status": "active",
"lastLogin": "now()",
"role": "user"
}
},
{
"condition": "query.status == 'inactive'",
"response": {
"status": "inactive",
"lastLogin": null,
"role": "guest"
}
},
{
"condition": "default",
"response": {
"status": "suspended",
"lastLogin": null,
"role": "guest"
}
}
]
}
]
}
メリットとデメリット
メリット:
- 直感的な構文: プログラミングのように記述できるため、可読性が高い。
- 条件分岐が明示的: if 文で分岐を直接記述できる。
- 動的生成が可能: API仕様の変更が即座に設定ファイルに反映される。
デメリット:
- DSLの設計とパーサの構築が必要: 独自構文の定義・解析のための初期コストが発生。
- 複雑なロジックの管理: 分岐が増えると構文が複雑化しやすい。
- デバッグが難しい: DSLの構文エラーやロジックミスの検出が困難になる可能性。
終わりに
他にも考慮する点がありますが、面白そうなアイディアだったので共有します。
時間とやる気がある方作ってみてはどうでしょうか?
Discussion