😽

プログラミングのように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