Go에서 코드 생성으로 백엔드 로직을 단순하게 만들기

Go에서 코드 생성으로 백엔드 로직을 단순하게 만들기

백엔드 개발에는 반복되는 코드가 늘 따라붙는다. 특히 데이터베이스 쿼리와 API 서버 주변이 그렇다. 그 코드를 손으로 쓰는 건 느리고 실수도 쉽다. 코드 생성 도구(codegen)는 선언적인 명세에서 타입 안전한 Go 코드를 자동으로 만들어 준다. 유지 보수할 코드도 줄어든다.

이 글에서는 두 가지 도구를 다룬다.

코드 생성을 쓰는 이유

sqlc로 SQL 코드 생성하기

sqlc란

sqlc는 .sql 파일을 읽고, 타입이 있는 매개변수와 결과로 쿼리를 실행하는 Go 함수를 생성한다.

sqlc 사용법

  1. 스키마와 쿼리를 작성한다.
  2. sqlc.yaml에서 입력과 출력을 설정한다.
  3. sqlc generate를 실행한다.

예시

users 테이블이 있다고 하자.

-- schema.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
);

이메일로 사용자를 찾는 쿼리는 다음과 같다.

-- queries.sql
-- name: GetUserByEmail :one
SELECT id, name, email FROM users WHERE email = $1;

sqlc.yaml 설정.

version: "1"
packages:
  - name: "db"
    path: "./db"
    queries: "./queries.sql"
    schema: "./schema.sql"
    engine: "postgresql"

실행:

sqlc generate

그러면 이런 메서드가 생성된다.

func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error)

백엔드에서는 이렇게 쓴다.

user, err := dbQueries.GetUserByEmail(ctx, "alice@example.com")
if err != nil {
    // handle error
}
fmt.Println("User:", user.Name)

SQL 문자열을 손으로 다루고 행을 스캔하던 작업이 사라진다. 코드가 더 깔끔하고 안전해진다.

oapi-codegen으로 서버 코드 생성하기

oapi-codegen이란

oapi-codegen은 OpenAPI 명세에서 Go 서버 코드와 클라이언트 코드를 생성한다. 인터페이스와 요청·응답 타입을 만들어 준다. 남는 일은 비즈니스 로직 구현뿐이다.

oapi-codegen 사용법

  1. OpenAPI YAML 또는 JSON 파일로 API를 정의한다.
  2. oapi-codegen을 실행한다.
  3. 생성된 인터페이스 메서드를 구현한다.

예시

작은 OpenAPI 명세 api.yaml을 준비한다.

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users/{email}:
    get:
      summary: Get user by email
      parameters:
        - name: email
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: User found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "404":
          description: User not found
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string

서버 코드를 생성한다.

oapi-codegen -generate types,server -package api -o api.gen.go api.yaml

이러면 요청·응답 타입(User struct)과 메서드를 가진 ServerInterface가 만들어진다.

GetUsersEmail(ctx context.Context, email string) (api.User, error)

인터페이스를 구현한다.

type ServerImpl struct {
    db *db.Queries
}
 
func (s *ServerImpl) GetUsersEmail(ctx context.Context, email string) (api.User, error) {
    user, err := s.db.GetUserByEmail(ctx, email)
    if err != nil {
        return api.User{}, err
    }
    return api.User{
        Id:    int64(user.ID),
        Name:  user.Name,
        Email: user.Email,
    }, nil
}

서버를 연결한다.

router := api.NewRouter(&ServerImpl{db: dbQueries})
http.ListenAndServe(":8080", router)

sqlc와 oapi-codegen을 함께 쓰는 이점

마무리

sqlc와 oapi-codegen을 함께 쓰면 Go 백엔드에서 데이터베이스 접근과 API 서버 코드를 자동화할 수 있다. 써야 할 보일러플레이트가 줄고 실행 중 오류도 줄어든다. 집중할 곳은 애플리케이션의 핵심 로직이다.

© Melvin Laplanche - All rights reserved.