Open6

goの練習がてらopenAPIドキュメントからコードを自動生成してアプリを作ってみる

Hirotsugu KawasakiHirotsugu Kawasaki

アプリの内容としてはシンプルにメモのCRUDが出来るアプリ。
DBには使い慣れたmysql、マイグレーションツールはsql-migrateを使ってみる。

Hirotsugu KawasakiHirotsugu Kawasaki

CRUDのapiをざっくりopenapiドキュメントに起こす

openapi: 3.0.3
info:
  title: memo-go
  version: 1.0.0
  description: メモのCRUD用API
servers:
  - url: 'http://localhost:8080'
    description: develop
paths:
  /memos:
    get:
      description: dbに登録されたメモのタイトル一覧を取得する
      responses:
        '200':
          description: Ok
          content:
            application/json:
              schema:
                type: array
                description: メモのタイトル一覧
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                      description: メモのid
                    title:
                      type: string
                      description: メモのタイトル
                  required:
                    - id
                    - title
        '500':
          $ref: '#/components/responses/500InternalServerError'
      operationId: getMemos
      summary: メモの一覧取得
    post:
      summary: メモの作成
      operationId: postMemos
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                    description: メモのid
                required:
                  - id
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                required:
                  - message
        '500':
          $ref: '#/components/responses/500InternalServerError'
      description: 送られてきた情報でメモを作成する
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: メモのタイトル
                content:
                  type: string
                  minLength: 1
                  maxLength: 21844
                  description: メモの中身
              required:
                - title
                - content
        description: 作成するメモの情報
  '/memo/{id}':
    get:
      summary: メモの詳細
      operationId: getMemo
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                    description: メモのid
                  title:
                    type: string
                    description: メモのタイトル
                  content:
                    type: string
                    description: メモの中身
                required:
                  - id
                  - title
                  - content
        '404':
          $ref: '#/components/responses/404NotFound'
        '500':
          $ref: '#/components/responses/500InternalServerError'
      description: 対象idのメモの詳細を取得する
      parameters: []
    post:
      summary: メモの更新
      operationId: postMemo
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 更新成功メッセージ
                required:
                  - message
        '404':
          $ref: '#/components/responses/404NotFound'
        '500':
          $ref: '#/components/responses/500InternalServerError'
      description: 送られてきた情報で対象idのメモを更新する
    parameters:
      - schema:
          type: integer
          minimum: 1
        name: id
        in: path
        required: true
        description: メモのid
    delete:
      summary: メモの削除
      operationId: deleteMemo
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 削除成功メッセージ
                required:
                  - message
        '404':
          $ref: '#/components/responses/404NotFound'
        '500':
          $ref: '#/components/responses/500InternalServerError'
      description: 対象idのメモを削除する
components:
  responses:
    500InternalServerError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                description: エラーメッセージ
            required:
              - message
    404NotFound:
      description: Not Found
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                description: エラーメッセージ
            required:
              - message

Hirotsugu KawasakiHirotsugu Kawasaki

openapi-generatorでgoのサーバー側のスケルトンを自動生成

> docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
    -i /local/openapi.yml \
    -g go-server \
    -o /local

生成されたファイル達

> tree
.
├── Dockerfile
├── api
│   └── openapi.yaml
├── go
│   ├── api.go
│   ├── api_default.go
│   ├── api_default_service.go
│   ├── error.go
│   ├── helpers.go
│   ├── impl.go
│   ├── logger.go
│   ├── model_inline_object.go
│   ├── model_inline_response_200.go
│   ├── model_inline_response_200_1.go
│   ├── model_inline_response_200_2.go
│   ├── model_inline_response_200_3.go
│   ├── model_inline_response_200_4.go
│   ├── model_inline_response_400.go
│   ├── model_inline_response_500.go
│   └── routers.go
├── go.mod
├── go.sum
├── main.go
└── openapi.yml
Hirotsugu KawasakiHirotsugu Kawasaki

docker環境の構築

docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: ./docker/app/Dockerfile
    volumes:
      - type: bind
        source: ./
        target: /go/src/app
    ports:
      - "8080:8080"
    depends_on:
      - db
    tty: true

  db:
    image: mysql/mysql-server:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: dev
      MYSQL_USER: dev
      MYSQL_PASSWORD: dev
      TZ: "Asia/Tokyo"
    volumes:
      - type: bind
        source: ./docker/db/my.cnf
        target: /etc/mysql/conf.d/my.cnf
      - type: volume
        source: db_data
        target: /var/lib/mysql
    ports:
      - 13306:3306

  swagger-editor:
    image: swaggerapi/swagger-editor
    ports:
      - "8001:8080"

  swagger-ui:
    image: swaggerapi/swagger-ui
    ports:
      - "8002:8080"
    volumes:
      - type: bind
        source: ./openapi.yml
        target: /openapi.yml
    environment:
      SWAGGER_JSON: /openapi.yml

volumes:
  db_data:
    driver: local
Dockerfile
FROM golang:1.17.2-alpine

WORKDIR /go/src/app

RUN apk update && apk add git
COPY go.mod go.sum ./
RUN go mod tidy
EXPOSE 8080

CMD ["go", "run", "main.go"]
my.cnf
[mysqld]
character_set_server            = utf8mb4
collation_server                = utf8mb4_ja_0900_as_cs
default_authentication_plugin   = mysql_native_password
default-time-zone               = SYSTEM
log_timestamps                  = SYSTEM
slow_query_log_file             = /tmp/mysqld.slow.log
slow_query_log                  = 1
long_query_time                 = 2

[mysql]
default-character-set = utf8mb4
Hirotsugu KawasakiHirotsugu Kawasaki

マイグレーション

sql-migrateをinstallするようにDockerfileを編集

Dockerfile
FROM golang:1.17.2-alpine

WORKDIR /go/src/app

RUN apk update && apk add git
COPY go.mod go.sum ./
ARG CGO_ENABLED=0
RUN go mod tidy && \
    go install github.com/rubenv/sql-migrate/...@latest
EXPOSE 8080

CMD ["go", "run", "main.go"]

sql-migrateの設定ファイル

dbconfig.yml
development:
    dialect: mysql
    datasource: dev:dev@tcp(db:3306)/dev?parseTime=true
    dir: migrations

コンテナの中で以下のコマンドを実行し、マイグレーションファイルを生成。

/go/src/app/migrate # sql-migrate new create_memo

作成したマイグレーションファイル

20211017103235-create_memo.sql

-- +migrate Up
CREATE TABLE IF NOT EXISTS memo (
    id int(15) AUTO_INCREMENT,
    title varchar(255),
    content text,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id));

-- +migrate Down
DROP TABLE IF EXISTS memo;

マイグレーションの実行

/go/src/app/migrate # sql-migrate up
Applied 1 migration

/go/src/app/migrate # sql-migrate status
+--------------------------------+-------------------------------+
|           MIGRATION            |            APPLIED            |
+--------------------------------+-------------------------------+
| 20211017103235-create_memo.sql | 2021-10-17 10:59:48 +0000 UTC |
+--------------------------------+-------------------------------+
Hirotsugu KawasakiHirotsugu Kawasaki

TODO

  • とりあえず直でdbにデータを入れる
  • sqlクライアントを使ってdbと接続し、参照APIを動くようにする
  • テスト書く
  • 他のAPIも動くようにする