CAP Java の API ドキュメントを OpenAPI(Swagger) で作成する


SAP CAP にて、CDS で API Endpoint を作成できるのは便利な一方、
CDS を書いていない方にとっては読みにくく、仕様として連携が難しい面があったため、
API ドキュメントを生成してみることにしました。

公式ドキュメント

https://cap.cloud.sap/docs/advanced/openapi

公式に記載されている通り、Node.js と違って Java の場合は CAP が良しなにやってくれるわけではないので、
自力で Spring Boot に統合してあげる必要があります。

幸い以下のサンプルコードも共有されているため、これを参考にしつつ、ドキュメントを生成してみます。

https://github.com/SAP-samples/cloud-cap-samples-java/commit/67f0ba618fc7da131d1a104f7a23e8b836e14d93

作業内容

1. srv/pom.xml に コンパイルコマンドを追加

公式サンプルの記述では以下のようになっています。

    <command>compile srv/cat-service.cds -2 openapi --openapi:url /api/browse >
        ${project.basedir}/src/main/resources/swagger/openapi.json</command>

しかしこの記述だと、特定の CDS( この例では cat-service.cds ) だけを対象としているため、
今回はすべてのドキュメントを出力するように変更します。
また、オプションによって図も出せるようなので、追加してみます。

<変更後>

    <command>compile srv --service all -o srv/src/main/resources/swagger --to openapi
        --openapi:diagram</command>

2. Swagger 向けの WebMvcConfigurer 実装クラスを作成する

こちらは公式そのままでOK。

SwaggerResourceConfig.java

3. Swagger 向けの WebSecurityConfigurerAdapter 継承クラスを作成する

こちらも公式そのままで。

SwaggerSecurityConfig.java

ドキュメント表示用の index.html を用意する

こちらも、公式サンプルに沿い、Swagger UI の html を導入します。

index.html

ただ、 pom.xml 記述時にすべての CDS を出力するようにしたため、サンプルそのままの Script では動きません。
そのため、出力される OpenAPI ドキュメントすべてを select box で選択できるようにします。

<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Swagger UI</title>
<!-- 中略 -->
<script>
  window.onload = function() {
    // 手動で追加。動的にできるかは今後試してみる
    const urls = [
      `BooksService.openapi3.json`,
      `OrdersService.openapi3.json`,
    ]

    const ui = SwaggerUIBundle({
      url: urls[0],
      dom_id: '#swagger-ui',
      deepLinking: true,
      presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIStandalonePreset
      ],
      plugins: [
        SwaggerUIBundle.plugins.DownloadUrl
      ],
      layout: "StandaloneLayout"
    })

    window.ui = ui

    // input を select に書き換え
    const input = document.getElementsByClassName('download-url-input')[0]
    const select = document.createElement('select')
    select.classList = input.classList

    urls.forEach (url => {
        const option = document.createElement('option')
        option.label = url
        option.value = url
        select.appendChild(option)
      }
    )

    // select 変更時のイベント登録
    select.addEventListener('change', (event) => {
      const url = event.target.value
      ui.specActions.updateUrl(url)
      ui.specActions.download(url)
    });

    input.parentElement.replaceChild(select, input)
    document.getElementsByClassName('download-url-button')[0].remove()
  }

</script>
</body>

</html>

動作確認

ここまでファイルを追加した上で、アプリを起動します。

mvn spring-boot:run

起動時にドキュメントを生成するように pom.xml を更新していますので、エラーが無ければ以下の通り、
srv/src/main/resources にサービスごとの json ファイルが作成されているはずです。

この状態で http://localhost:8080/swagger/index.html にアクセスすると、OpenAPI ドキュメントが参照できます。


※ diagram もついてます。

また、その他のAPIもリストから選択可能です。

これで、CDS ファイルをそのまま共有するよりはわかりやすいドキュメントが提供できるようになりました。
ただ、このままだと本番環境でもドキュメントが公開されますので、その点は注意が必要です。

おわりに

今回ドキュメントを生成した元となった CDS は、以下の通り項目を絞ることも、イベントを制限することもしていません。

service BooksService {

    entity Books as select from my.Books
}

そのため、公開されるサービスは GET, POST, DELETE 等多くの処理を実行することができてしまいます。

「これでOK」となるケースはあまりないと思いますので、次回以降で以下の内容にも取り組んでみます。

https://cap.cloud.sap/docs/guides/providing-services#single-purposed-services