GoSuda

Go and the OpenAPI Ecosystem

By iwanhae
views ...

Introduction

When developing a Production Backend server with Go, one of the first challenges most developers encounter is:

How do I document my APIs...?

Upon researching this issue, one quickly realizes the benefits of writing documentation compliant with the OpenAPI specification, and naturally, one begins searching for libraries that integrate with OpenAPI. However, even after making this decision, another problem arises:

There are many OpenAPI-related libraries... which one should I use...?

This document is a brief introduction to libraries, written for Go beginners who are experiencing this situation. It is written as of late 2024, and it is recommended that you always check the latest updates as the language ecosystem is constantly evolving.

Strategies of Libraries Regarding OpenAPI

As you may already know, OpenAPI is a specification for clearly defining and documenting REST APIs. It defines API endpoints, requests, and response formats in YAML or JSON, which helps developers, as well as front-end and back-end teams, automate code generation, reducing meaningless repetition and minor human errors.

To seamlessly integrate OpenAPI with a project, libraries in the Go ecosystem largely adopt one of the following three strategies:

1. Combining Go Comments into OpenAPI Specification Documents

One of the challenging aspects of developing APIs according to OpenAPI is that the actual documentation and the code implementing it exist as separate files in completely different locations. As a result, it is surprisingly common to update the code without updating the documentation, or update the documentation without updating the code.

For a simple example:

  1. You modify the logic for an API in a file such as ./internal/server/user.go.
  2. The actual documentation resides in ./openapi3.yaml, and you might accidentally forget to update it.
  3. If you submit a Pull Request without recognizing these changes and have it reviewed by colleagues.
  4. Reviewers will also not see any changes in ./openapi3.yaml. Thus, a mishap can occur where the actual API implementation is changed while the API specification remains the same.

Writing API documentation in the form of Go comments can alleviate this problem to some extent. Since the code and documentation are in one place, you can update the comments along with the code. There are tools that automatically generate OpenAPI specification documents based on these comments.

A representative project is Swag. Swag parses Go code comments and generates OpenAPI 2 format documents. The usage is simple. You write comments in the format defined by each library above the handler functions.

 1// @Summary Create user
 2// @Description Creates a new user.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "User information"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

By writing comments like this, the Swag CLI parses these comments and generates an OpenAPI 2 document. This process is typically done during CI, and the generated OpenAPI specification document is distributed to Git repositories, final builds, or separate external API documentation management systems to be used for collaboration with other projects.

Advantages:

  • Since comments are with the code, the possibility of the actual code and documentation diverging is reduced.
  • You can easily and freely document using only comments, without separate tools or complex configurations.
  • Since comments do not affect the actual API logic, it is beneficial for adding temporary features that are not suitable for public documentation.

Disadvantages:

  • The readability of a single code file can decrease as the number of comment lines increases.
  • It may be difficult to express the entire API specification using the comment format.
  • Since documentation does not enforce code, there is no guarantee that the OpenAPI document and the actual logic match.

2. Generating Go Code from OpenAPI Specification Documents

There is also a method of placing the Single Source of Truth (SSOT) on the document side, not the Go code. This is the method of first defining the OpenAPI specification and then generating Go code based on the defined content. Since the API specification directly generates code, it can enforce API design first in the development culture, and since defining the API specification is the first step in the development process, it has the advantage of preventing the mishap of recognizing missed parts and modifying the entire code along with the API specification change after development is completed.

Representative projects that adopt this approach include oapi-codegen and OpenAPI Generator. The usage is simple.

  1. Write a YAML or JSON document according to the OpenAPI specification.
  2. Execute the CLI.
  3. The corresponding Go stub code is generated.
  4. Now, you just need to implement the detailed logic for individual APIs so that this stub can be used.

The following is an example of code generated by oapi-codegen:

1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3	// ...
4	// Returns all pets
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

Using the above interface, the code generated by oapi-codegen performs query parameters, header, body parsing, and Validation logic, and calls the appropriate method declared in the interface. The user only needs to implement the implementation of the above interface to complete the work required for API implementation.

Advantages:

  • Because specifications come first and development proceeds afterward, it is advantageous to proceed with tasks in parallel when multiple teams collaborate.
  • Code for parts that used to be repetitive and tedious is automatically generated, which increases work efficiency while still being beneficial for debugging.
  • It is easy to ensure that the form of the documentation and code always match.

Disadvantages:

  • If you are unfamiliar with the OpenAPI specification itself, there is a learning curve initially.
  • Since the form of the code handling the API is automatically generated by the project, it may be difficult to respond when customization is required.

Author's comment. As of October 2024, the Go code generated by the OpenAPI Generator enforces not only the API logic but also the entire project structure, and the project's structure is rigid, so it generates code that is not suitable for adding various functions required in a real Production environment. If you adopt this approach, we strongly recommend using oapi-codegen. The author uses oapi-codegen + echo + StrictServerInterface.

3. Generating OpenAPI Specification Documents from Go Code

When dozens or hundreds of people are developing the same server, an inevitable issue is that the consistency of individual APIs can be broken. As a direct example, if the specifications for over 100 API endpoints are declared in a single OpenAPI YAML file, that file will become a monster exceeding 10,000 lines, and when declaring a new API endpoint, you will inevitably declare the same model redundantly, or omit some fields, or create Path names that do not conform to conventions, and the overall consistency of the API will begin to break.

To solve this issue, you can appoint a separate owner to manage the OpenAPI YAML, or develop a Linter to automatically catch issues during CI, but you can also define a Domain-specific language (DSL) in Go to enforce all APIs to have consistent uniformity.

A representative project that uses this technique is Kubernetes (which builds it in-house without a separate library). You can also use projects such as go-restful and goa. The following is an example of using goa:

 1var _ = Service("user", func() {
 2    Method("create", func() {
 3        Payload(UserPayload)
 4        Result(User)
 5        HTTP(func() {
 6            POST("/users")
 7            Response(StatusOK)
 8        })
 9    })
10})

By writing compilable Go code like above, you can simultaneously complete the implementation of the POST /users API and the definition of the documentation.

Advantages:

  • Since everything comes from code, it is easy to maintain API consistency across the entire project.
  • By utilizing Go's strong type system, you can obtain a more accurate and indisputable specification than when using all the features of OpenAPI3.

Disadvantages:

  • You must learn the DSL defined by each framework, and it may be difficult to apply it to existing code.
  • You must forcefully follow the rules proposed by the framework, so freedom and flexibility may be reduced.

Concluding Remarks

Each method has its advantages and disadvantages, and it is important to choose a method suitable for the project's requirements and team preferences. The most important thing is not which method is better, but rather to assess what is the most suitable solution for your current situation and to increase development productivity for quicker departures and satisfactory work-life balance.

Although this article is written as of October 2024, the Go and OpenAPI ecosystems are constantly evolving. Therefore, consider the time gap between when this article was written and when you are reading it, and continuously follow up on the updates and the changed advantages and disadvantages of each library and project.

Have a happy Go life~ 😘