1. 概要

Spring RESTDocsとOpenAPI3.0は、RESTAPIのAPIドキュメントを作成する2つの方法です。

このチュートリアルでは、それらの相対的な長所と短所を調べます。

2. 起源の簡単な要約

Spring REST Docs は、RESTfulAPIの正確なドキュメントを作成するためにSpringコミュニティによって開発されたフレームワークです。 テスト駆動型のアプローチを採用しており、ドキュメントはSpring MVCテスト、SpringWebfluxのWebTestClient、、またはREST-Assuredのいずれかとして記述されています。

テスト実行の出力はAsciiDocファイルとして作成され、 Asciidoctor を使用してまとめて、APIを説明するHTMLページを生成できます。 TDD方式に従っているため、Spring REST Docsは、エラーが発生しにくいコード、やり直しの削減、フィードバックサイクルの高速化など、すべての利点を自動的にもたらします

一方、 OpenAPI は、Swagger2.0から生まれた仕様です。 これを書いている時点での最新バージョンは3.0であり、多くの既知の実装があります。

他の仕様と同様に、OpenAPIは、その実装が従うべき特定の基本ルールを定めています。 簡単に言えば、すべての OpenAPI実装は、JSONまたはYAML形式のいずれかでドキュメントをJSONオブジェクトとして生成することになっています。

このJSON/YAMLを取り込んでUIを吐き出し、APIを視覚化してナビゲートする多くのツールもあります。 これは、たとえば、検収試験の際に役立ちます。 ここでのコードサンプルでは、 springdoc –Spring Bootを備えたOpenAPI3のライブラリを使用します。

2つを詳細に説明する前に、文書化するAPIを簡単に設定しましょう。

3. RESTAPI

Spring Bootを使用して基本的なCRUDAPIをまとめましょう。

3.1. リポジトリ

ここで使用するリポジトリは、モデルFooを備えた最低限のPagingAndSortingRepositoryインターフェイスです。

@Repository
public interface FooRepository extends PagingAndSortingRepository<Foo, Long>{}

@Entity
public class Foo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    
    @Column(nullable = false)
    private String title;
  
    @Column()
    private String body;

    // constructor, getters and setters
}

また、schema.sqldata.sqlを使用してリポジトリロードします。

3.2. コントローラー

次に、簡潔にするために実装の詳細をスキップして、コントローラーを見てみましょう。

@RestController
@RequestMapping("/foo")
public class FooController {

    @Autowired
    FooRepository repository;

    @GetMapping
    public ResponseEntity<List<Foo>> getAllFoos() {
        // implementation
    }

    @GetMapping(value = "{id}")
    public ResponseEntity<Foo> getFooById(@PathVariable("id") Long id) {
        // implementation
    }

    @PostMapping
    public ResponseEntity<Foo> addFoo(@RequestBody @Valid Foo foo) {
        // implementation
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteFoo(@PathVariable("id") long id) {
        // implementation
    }

    @PutMapping("/{id}")
    public ResponseEntity<Foo> updateFoo(@PathVariable("id") long id, @RequestBody Foo foo) {
        // implementation
    }
}

3.3. アプリケーション

そして最後に、ブートアプリ:

@SpringBootApplication()
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. OpenAPI / Springdoc

次に、springdocFooRESTAPIにドキュメントを追加する方法を見てみましょう。

JSONオブジェクトとそのオブジェクトに基づくAPIのUI視覚化を生成することを思い出してください。

4.1. 基本的なUI

まず、Mavenの依存関係をいくつか追加します。JSONを生成するための springdoc-openapi-data-rest と、UIをレンダリングするためのspringdoc-openapi-uiです。

このツールは、APIのコードをイントロスペクトし、コントローラーメソッドのアノテーションを読み取ります。 これに基づいて、 http:// localhost:8080 / api-docs/で公開されるAPIJSONが生成されます。 また、 http:// localhost:8080 /swagger-ui-custom.htmlで基本的なUIを提供します。

ご覧のとおり、コードをまったく追加しなくても、Fooスキーマに至るまでAPIの美しい視覚化が得られました。 試してみるボタンを使用すると、操作を実行して結果を表示することもできます。

さて、 APIに実際のドキュメントを追加したい場合はどうなりますか? APIとは何か、そのすべての操作の意味、入力する必要があるもの、期待される応答についてはどうでしょうか。

これについては、次のセクションで説明します。

4.2. 詳細なUI

まず、APIに一般的な説明を追加する方法を見てみましょう。

そのために、OpenAPIBeanをブートアプリに追加します。

@Bean
public OpenAPI customOpenAPI(@Value("${springdoc.version}") String appVersion) {
    return new OpenAPI().info(new Info()
      .title("Foobar API")
      .version(appVersion)
      .description("This is a sample Foobar server created using springdocs - " + 
        "a library for OpenAPI 3 with spring boot.")
      .termsOfService("http://swagger.io/terms/")
      .license(new License().name("Apache 2.0")
      .url("http://springdoc.org")));
}

次に、API操作にいくつかの情報を追加するために、いくつかのOpenAPI固有のアノテーションでマッピングを装飾します。

getFooById。を説明する方法を見てみましょう。これは別のコントローラーFooBarController内で行います。これは、FooControllerに似ています。

@RestController
@RequestMapping("/foobar")
@Tag(name = "foobar", description = "the foobar API with documentation annotations")
public class FooBarController {
    @Autowired
    FooRepository repository;

    @Operation(summary = "Get a foo by foo id")
    @ApiResponses(value = {
      @ApiResponse(responseCode = "200", description = "found the foo", content = { 
        @Content(mediaType = "application/json", schema = @Schema(implementation = Foo.class))}),
      @ApiResponse(responseCode = "400", description = "Invalid id supplied", content = @Content), 
      @ApiResponse(responseCode = "404", description = "Foo not found", content = @Content) })
    @GetMapping(value = "{id}")
    public ResponseEntity getFooById(@Parameter(description = "id of foo to be searched") 
      @PathVariable("id") String id) {
        // implementation omitted for brevity
    }
    // other mappings, similarly annotated with @Operation and @ApiResponses
}

次に、UIへの影響を見てみましょう。

したがって、これらの最小限の構成で、APIのユーザーは、APIの内容、使用方法、および期待される結果を確認できます。 コードをコンパイルしてブートアプリを実行するだけで済みました。

5. SpringRESTドキュメント

RESTドキュメントは、APIドキュメントに対するまったく異なる考え方です。 前に説明したように、プロセスはテスト駆動型であり、出力は静的HTMLページの形式です。

この例では、SpringMVCテストを使用してドキュメントスニペットを作成します。

最初に、spring-restdocs-mockmvc依存関係とasciidocMavenプラグインpomに追加する必要があります。

5.1. JUnit5テスト

次に、ドキュメントを含むJUnit5テストを見てみましょう。

@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class })
@SpringBootTest(classes = Application.class)
public class SpringRestDocsIntegrationTest {
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    public void setup(WebApplicationContext webApplicationContext, 
      RestDocumentationContextProvider restDocumentation) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
          .apply(documentationConfiguration(restDocumentation))
          .build();
    }

    @Test
    public void whenGetFooById_thenSuccessful() throws Exception {
        ConstraintDescriptions desc = new ConstraintDescriptions(Foo.class);
        this.mockMvc.perform(get("/foo/{id}", 1))
          .andExpect(status().isOk())
          .andDo(document("getAFoo", preprocessRequest(prettyPrint()), 
            preprocessResponse(prettyPrint()), 
            pathParameters(parameterWithName("id").description("id of foo to be searched")),
            responseFields(fieldWithPath("id")
              .description("The id of the foo" + 
                collectionToDelimitedString(desc.descriptionsForProperty("id"), ". ")),
              fieldWithPath("title").description("The title of the foo"), 
              fieldWithPath("body").description("The body of the foo"))));
    }

    // more test methods to cover other mappings

}

このテストを実行した後、指定されたAPI操作に関する情報を含むいくつかのファイルを targets/generated-snippetsディレクトリに取得します。 特に、 whenGetFooById_thenSuccessful は、ディレクトリ内のgetAFooフォルダに8つのadocを提供します。

これがサンプルhttp-response.adocで、もちろん応答本文が含まれています。

[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 60

{
  "id" : 1,
  "title" : "Foo 1",
  "body" : "Foo body 1"
}
----

5.2. fooapi.adoc

次に、これらすべてのスニペットを組み合わせて、適切に構造化されたHTMLを形成するマスターファイルが必要です。

それをfooapi.adocと呼び、その一部を見てみましょう。

=== Accessing the foo GET
A `GET` request is used to access the foo read.

==== Request structure
include::{snippets}/getAFoo/http-request.adoc[]

==== Path Parameters
include::{snippets}/getAFoo/path-parameters.adoc[]

==== Example response
include::{snippets}/getAFoo/http-response.adoc[]

==== CURL request
include::{snippets}/getAFoo/curl-request.adoc[]

asciidoctor-maven-pluginを実行した後、target/generated-docsフォルダーに最終的なHTMLファイルfooapi.htmlを取得します。

そして、これはブラウザで開いたときにどのように見えるかです:

6. 重要なポイント

両方の実装を確認したので、長所と短所を要約してみましょう。

springdoc を使用すると、RESTコントローラーのコードが乱雑になり、読みやすさが低下しました。 また、ドキュメントはコードと緊密に結合されており、本番環境に移行します。

言うまでもなく、ここでのドキュメントの維持は別の課題です。APIの何かが変更された場合、プログラマーは常に対応するOpenAPIアノテーションを更新することを忘れないでしょうか。

一方、 REST Docsは、他のUIほどキャッチーに見えず、受け入れテストに使用することもできません。 しかし、それには利点があります。

特に、 Spring MVCテストが正常に完了すると、スニペットが得られるだけでなく、他の単体テストと同様にAPIが検証されます。 これにより、APIの変更に対応するドキュメントの変更が必要になります。 また、ドキュメントコードは実装から完全に分離されています。

しかし、逆に言えば、ドキュメントを生成するためにさらにコードを作成する必要がありました。 まず、OpenAPIアノテーションとほぼ同じくらい冗長なテスト自体、そして次に、マスターadocです。

また、最終的なHTMLを生成するには、さらに多くの手順が必要です。最初にテストを実行し、次にプラグインを実行します。  Springdoc では、ブートアプリを実行するだけで済みました。

7. 結論

このチュートリアルでは、OpenAPIベースのspringdocとSpringRESTDocsの違いを確認しました。 また、この2つを実装して、基本的なCRUDAPIのドキュメントを生成する方法についても説明しました。

要約すると、どちらにも長所と短所があり、どちらを使用するかの決定は、特定の要件に従う必要があります。

いつものように、ソースコードはGitHubから入手できます。