728x90

sqlc 사용기: Go + PostgreSQL, SQL 기능을 하는 함수 생성

 

 

 

 

1. Intro

sqlc라 최고라는 말을 듣고 적용해보았다!.. 이 도구는 SQL 스키마와 쿼리를 기반으로 타입 안전한 Go 코드를 생성해주어, 안정성과 개발 생산성을 높여준다고 합니다. 이번 글에서는 sqlc를 사용하여 PostgreSQL 데이터베이스와 상호작용하는 방법에 대해 살펴보겠습니다.

2. sqlc 설정

sqlc를 사용하기 위해서는 먼저 sqlc.yaml 설정 파일을 생성해야 합니다. 이 파일에는 데이터베이스 엔진, 쿼리 파일, 스키마 파일, 그리고 생성된 Go 코드의 출력 위치 등을 지정합니다.

version: "2"
cloud:
  project: "xxxxxxxxxxx"
sql:
  - engine: "postgresql"
    queries: "query.sql"
    schema: "schema.sql"
    database:
      uri: "postgres://postgres:xxxxxxxx@xxx-rds.xxxxxxx.ap-xxxxxxxx-2.rds.amazonaws.com:5432/postgres"
    gen:
      go:
        package: "db"
        out: "db"
        sql_package: "pgx/v5"

 

 

3. 스키마 정의

schema.sql 파일에 데이터베이스 테이블 구조를 정의합니다. 예를 들어, authors 테이블을 다음과 같이 정의할 수 있습니다

CREATE TABLE authors (
  id   BIGSERIAL PRIMARY KEY,
  name text      NOT NULL,
  bio  text
);

테이블 정보를 담은 DDL을 실행해 줍니다. 자동으로 마이그레이션을 해주고, 마이그레이션 파일을 저장해주는 라이브러리가 있다고 하는데, 다음에 적용해볼 예정입니다.

 

4. 쿼리 작성

query.sql 파일에 필요한 데이터베이스 쿼리를 작성합니다. sqlc는 이 쿼리들을 분석하여 Go 함수로 변환합니다.

-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;

-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;

-- name: CreateAuthor :one
INSERT INTO authors (
  name, bio
) VALUES (
  $1, $2
)
RETURNING *;

-- name: UpdateAuthor :exec
UPDATE authors
  set name = $2,
  bio = $3
WHERE id = $1;

-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;

 

 

5. 코드 생성

sqlc 설정이 완료되면, 다음 명령어를 실행하여 Go 코드를 생성합니다

sqlc generate

이 명령은 db/ 폴더 내에 query.sql.go, models.go, db.go 파일을 생성합니다. 이 파일들에는 데이터베이스 SQL 구문을 실행하기 위한  함수들이 포함되어 있습니다.


6. 생성된 코드 사용

생성된 코드를 사용하여 데이터베이스 작업을 수행할 수 있습니다. 예를 들어

// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.26.0
// source: query.sql

package db

import (
	"context"

	"github.com/jackc/pgx/v5/pgtype"
)

const createAuthor = `-- name: CreateAuthor :one
INSERT INTO authors (
  name, bio
) VALUES (
  $1, $2
)
RETURNING id, name, bio
`

type CreateAuthorParams struct {
	Name string
	Bio  pgtype.Text
}

func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
	row := q.db.QueryRow(ctx, createAuthor, arg.Name, arg.Bio)
	var i Author
	err := row.Scan(&i.ID, &i.Name, &i.Bio)
	return i, err
}

const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1
`

func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
	_, err := q.db.Exec(ctx, deleteAuthor, id)
	return err
}

const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = $1 LIMIT 1
`

func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
	row := q.db.QueryRow(ctx, getAuthor, id)
	var i Author
	err := row.Scan(&i.ID, &i.Name, &i.Bio)
	return i, err
}

const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`

func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
	rows, err := q.db.Query(ctx, listAuthors)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []Author
	for rows.Next() {
		var i Author
		if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const updateAuthor = `-- name: UpdateAuthor :exec
UPDATE authors
  set name = $2,
  bio = $3
WHERE id = $1
`

type UpdateAuthorParams struct {
	ID   int64
	Name string
	Bio  pgtype.Text
}

func (q *Queries) UpdateAuthor(ctx context.Context, arg UpdateAuthorParams) error {
	_, err := q.db.Exec(ctx, updateAuthor, arg.ID, arg.Name, arg.Bio)
	return err
}
import (
    "context"
    "your-project/db"
)

func main() {
    // 데이터베이스 연결 설정
    conn, err := pgx.Connect(context.Background(), "your-database-url")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close(context.Background())

    queries := db.New(conn)

    // 새 저자 생성
    author, err := queries.CreateAuthor(context.Background(), db.CreateAuthorParams{
        Name: "John Doe",
        Bio: pgtype.Text{String: "A prolific writer", Valid: true},
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created author: %v\n", author)

    // 저자 목록 조회
    authors, err := queries.ListAuthors(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    for _, a := range authors {
        fmt.Printf("Author: %s\n", a.Name)
    }
}

sqlc를 사용하면 데이터베이스 스키마, SQL 쿼리와 Go 코드 사이의 불일치를 줄이고, type-safe을 보장하며, 개발 생산성을 향상시킬 수 있습니다. 또한 데이터베이스 스키마 변경 시 자동으로 코드를 업데이트할 수 있어 유지보수가 용이하다고 합니다.

결론적으로, sqlc는 Go와 PostgreSQL을 함께 사용하는 프로젝트에서 강력한 도구가 될 수 있습니다. 타입 안전한 데이터베이스 액세스 코드를 자동으로 생성함으로써 개발자는 비즈니스 로직에 더 집중할 수 있게 되며, 데이터베이스 관련 오류를 크게 줄일 수 있는 장점이 있다고 합니다.

읽어주셔서 감사합니다 ☺️

 

 

 

728x90

Golang으로 백엔드 개발 도전기

Go 언어

 

1. Intro

간단한 백엔드를 만들일이 있어서 고민을 하다가, 유튜브 알고리즘에 뜬 아래 두 동영상을 보고 Golang Backend를 공부해야겠다고 다짐했습니다. 약 4년전, 공군 개발병 복무 시절, Golang + go gin 으로 간단한 REST API를 사지방에서 만들어보고 초당 몇번의 요청이 가능한지 테스트해보고 빨라서 놀랐던 기억이 있었는데, 그 시절이 떠오르네요.
[Golang 도입, 그리고 4년 간의 기록 / Golang과 함께 서버 레이턴시를 500배 개선한 후기] 
https://www.youtube.com/watch?v=75X_eBW0mog

https://www.youtube.com/watch?v=NVqVS64qClk

2. 왜 Golang인가?

간단한 백엔드 개발을 위한 언어를 선택하는 과정에서 가장 중요하게 생각한 것은 성능이 빠른지와 단순한지였습니다. Golang은 다음과 같은 매력적인 장점들이 있습니다.

빠른 컴파일 속도
Golang의 빠른 컴파일 속도는 개발 생산성을 크게 향상시킵니다. 공군 시절 Spring 프로젝트를 수정하고 컴파일 하는데 1분 가까이 걸려서 멍때렸던 기억이 있었는데, 아직 경험해보지 못했지만 go는 프로젝트가 커져도 빠른 컴파일이 된다고 합니다.
뛰어난 동시성 처리
Go routine과 채널을 통한 동시성 제어는 Golang의 큰 장점 중 하나입니다. 요즘 Sass(Software as a service)가 많아지면서 Third party app(GPT etc..)과 API 연동할 일이 많은데 이때 강점을 가집니다.
언어의 단순성
간결하고 읽기 쉬운 문법으로 빠르게 학습할 수 있습니다. 사실 다른 빠른 언어들도 있지만, 개인적으로 go 언어가 이해가 잘되었습니다.
성능 최적화
메모리와 CPU 사용을 쉽게 최적화할 수 있다고 합니다. 고성능 애플리케이션 개발에 많이 사용된다고 합니다.

물론, Golang에도 몇 가지 단점이 있습니다..

에러 처리의 복잡성
모든 에러를 명시적으로 처리해야 해서 코드가 길어질 수 있습니다.
GC 관련 이슈
대규모 시스템에서는 가비지 컬렉션으로 인한 성능 저하가 발생할 수 있습니다.
제네릭 지원 부족
제네릭 기능의 부재로 인해 코드 재사용성이 떨어질 수 있습니다.
특정 패턴 강제
에러 처리 등에서 특정 패턴을 따라야 하는 경우가 많습니다.

3. 선택한 기술 스택

Golang 커뮤니티를 탐색하면서 많은 개발자들이 추천하거나 github star가 많은 라이브러리를 찾았습니다. 제가 선택한 기술 스택은 다음과 같습니다:

Go Gin
경량화되고 빠른 웹 프레임워크로, RESTful API 개발에 적합합니다.
SQLC
SQL 쿼리를 Go 코드로 변환해주는 도구로, 타입 안정성과 성능을 모두 잡을 수 있습니다.
PostgreSQL with RDS
요즘 많이 쓰는 관계형 데이터베이스입니다.
Docker
개발 환경의 일관성을 유지할 수 있습니다.
Github Action CI/CD
easy..!

4. 특이했던 점

NestJS에서 사용했었던 schema.prisma처럼 migration 폴더에 데이터베이스 정보(PK, FK, Constraint, relation, type 등)를 기술하는 것. @Controller, @Service 같은 어노테이션이 없는것. pointer star (*), & 등 C 같은 느낌. 특이한 for문 등. 내일은 AWS RDS 데이터베이스 만들고, 연결해봐야겠다. 그리고 메모장같은 간단한 CRUD를 만들어보고 GPT나 Claude API 연동 시도해봐야겠다..! 

 
읽어주셔서 감사합니다 😊
 
 

+ Recent posts