golang 백엔드 개발을 해보자 1일차 - DB Schema 작성, Docker Postgres, migration script 만들기

1

Golang_Backend

목록 보기
1/2

golang backend master

go는 정말 재밌는 언어이다. 이 언어로 backend 개발을 해보고 싶고, 잘해보고 싶어 유튜브 영상들을 찾아보았다. 그 중 가장 괜찮은 강의를 가져와봤다.

https://www.youtube.com/watch?v=rx6CPDK_5mU&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE

현재 유튜브에서 강의가 공개되어있고, 얼마전 udemy에서 똑같은 레퍼토리로 강의를 올리셨다. 유튜브에 있는 커뮤니티에 udemy 링크로 free 강좌를 뿌렸던 걸로 기억난다. 그냥 유튜브로 영자막 켜놓고 봐도 문제없다.

첫 강의는 2020년이지만 마지막 강의는 2022년이고 설계 - 개발 - 배포 과정을 모두 다룬다. 그리고 golang 백엔드 개발에서 가장 대표적인 gin을 다룬다. 오늘부터 공부를 시작해보려고 한다.

1. Design DB schema and generate SQL code with dbdiagram.io

이 강의는 간단한 banking 시스템을 만들어본다고 한다. 이에 필요한 DB Schema를 만들기 위해, dbdiagram.io를 사용한다.

https://dbdiagram.io/home

db와 관련 간단한 스크립트를 통해 db 스키마를 디자인하고 시각화할 수 있는 좋은 툴이다. 앞으로도 잘 활용해보도록 해야겠다.

첫 화면은 다음과 같이 나온다. 왼쪽의 스크립트를 통해서 오른쪽의 스키마를 만드는 것이다.

우리가 만들어야할 데이터들은 3가지이다.

  1. accounts(id, owner, balance, currency, created_at) : 유저의 계정으로 소유자 이름, id, 잔액, 어떤 통화를 쓰는 지, 언제 만들어졌는 지가 필요하다.
  2. entries(id, account_id, amount, created_at) : 유저의 통장에 찍히는 거래 정보 내역이다. 즉 입출금 내역이다. account_id를 통해 account와 관계를 맺어주고, 얼마가 나갔고, 언제나갔는 지가 필요하다. accounts와 one-to-many 관계이다.
  3. transfers(id, from_account_id, to_account_id, amount, created_at) : 유저가 돈을 송금한 내역이다. from_account_id에서 to_account_id로 amount만큼 돈을 송금하는 것이다. accounts와 one-to-many 관계이다.

이를 정리하여 만들어보면 다음과 같다.

Table accounts as A {
  id bigserial [pk]
  owner varchar [not null]
  currency varchar [not null]
  balance bigint [not null]
  created_at timestamptz [not null, default: `now()`]
  
  indexes{
    owner
  }
}

Table entries {
  id bigserial [pk]
  account_id bigint [ref: > A.id, not null]
  amount bigint [not null, note: 'negative or positive']
  created_at timestamptz [not null, default: `now()`]
  
  indexes{
    account_id
  }
}

Table transfers {
  id bigserial [pk]
  from_account_id bigint [ref: > A.id, not null]
  to_account_id bigint [ref: > A.id, not null]
  amount bigint [not null, note: 'negative or positive']
  created_at timestamptz [not null, default: `now()`]
  
  indexes{
    from_account_id
    to_account_id
    (from_account_id, to_account_id)
  }
}

다음의 코드를 입력하면 아래의 결과가 나온다.

이제 상단 바에 export 버튼을 누르고 export to postgreSQL을 누르면 파일을 다운도르할 수 있게되는데 테이블 생성과 관계에 관해 적혀있는 것을 확인할 수 있다.

또한, pdf로 보내기를 누르면 다이어그램을 가져올 수 있고 mysql을 쓰고 싶다면 export to mysql을 누르면 된다. 또한, 링크가 공유되므로 같은 팀끼리 자료를 공유하기에 좋다.

2. install & use docker + postgres + tableplus to create DB schema

2.1 docker 설치

https://www.docker.com/get-started

공식 다운로드 홈페이지에 가서 자신의 os에 맞는 도커 인스톨 파일을 설치해주면 된다. 필자는 MAC M1을 사용하기 때문에 mac processor 전용으로 했다.
예전에 윈도우로 사용할 때는 꽤 불편한 과정을 거쳤던 것 같았다. 그래서 윈도우에서 도커 사용을 포기하고 우분투로 넘어가서 도커썼었다.

mac의 경우 다음처럼 drag-and-drop으로 설치가 완료된다.

docker를 설치하고 spotlight를 통해 docker를 실행하면 위에 다음과 같이 상태가 표시될 것이다.

현재 도커가 잘 작동 중임을 확인할 수 있다.

  • 현재 도커에서 실행 중인 컨테이너를 확인하는 방법은 다음과 같다.
  1. terminal에 접속
docker ps
  • image검색
docker images

현재는 다운받은 이미지가 없을 것이다. postgres 이미지를 다운받기위해 docker hub로 가자

2.2 docker postgreSQL 설치

https://hub.docker.com/_/postgres

위 링크는 postgres image 공식 페이지이다.

  1. postgreSQL image install

기본적인 docker에서 image를 가져오는 명령어는 다음과 같다.

docker pull <image>:<tag>

image는 docker hub에서 가져온 공식 image 이름이고, tag는 버전 등의 추가정보이다.

우리는 postgres version 12를 사용할 것이고, 보다 가벼운 컨테이너인 alpine을 사용할 것이다.

docker pull postgres:12-alpine

설치가 완료된 후 다시 docker images하면 이미지가 잘 설치되었다는 것을 확인할 수 있다.

이제 이미지를 통해 컨테이너를 만들어보자

  1. postgres container

컨테이너를 만드는 명령어는 다음과 같다.

docker run --name <container-name> -e <ENV_VARIABLE=value> -p <host_ports:container_ports> -d <image-name>

-name 플래그는 이미지로 부터 만들어질 컨테이너의 이름을 짓는 것이다. -e플래그는 뒤에 environment_variable를 설정하는 것이다. 자세한 설정은 postgres image 공식 홈페이지에 나와있다. -d는 detach mode로 백그라운드에서 해당 컨테이너를 돌리겠다는 것이다. 즉, 터미널에 접속해있지 않겠다는 것이다. 마지막 <image-name>에 우리가 컨테이너화 할 이미지 이름을 적어주어야 한다.

https://hub.docker.com/_/postgres 에서 How to extend this image부분을 확인해보자 그러면 환경변수로 어떤 것을 설정할 수 있는 지 확인할 수 있는데, 우리의 경우는 비밀번호를 여기에 설정해주도록 하자. 비밀번호 환경변수는 POSTGRES_PASSWORD이다. 또한 추가적으로 유저 이름도 적어주도록 하지 즉 POSTGRES_USERroot로 주도록 하자

-p는 포트를 의미한다. 기본적으로 docker에서 실행되는 프로그램들은 host(내 컴퓨터 자체)에서 돌아가는 네트워크와 다르다. 따라 이들 간에는 연결이 필요한데 host의 몇 번 포트를 통해 docker에서 작동 중인 몇 번 포트로 연결하도록 한다. 로 이해하면 된다. 즉 도커는 하나의 bridge 역할을 하여 호스트와 컨테이너 사이를 이어주는 것이다.

docker run --name postgres12 -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:12-alpine

다음의 명령어를 입력하면 긴 컨테이너 id가 반환되고 컨테이너가 만들어진 것을 확인할 수 있다.

docker ps

다음의 명령어를 통해 컨테이너가 잘 만들어졌는 지 확인해보도록 하자

잘만들어졌다면 우리가 생성한 이름으로 만들어졌음을 확인할 수 있을 것이다.

이제 컨테이너가 만들어졌으니 여기에 접속해보고 콘솔에 접속해보도록 하자

docker exec -it <container_name or id> <command>[args]

-it을 통해 컨테이너와 interaction할 수 있다.

docker exec -it postgres12 psql -U root

command로 psql(postgreSQL console)을 켜주고 root 유저에 접근하도록 한다.

이제 도커에 컨테이너에 있는 postgresSQL 서버와 인터렉션하면 되는데 여간 귀찮은 일이 아니다. 그래서 우리는 TablePlus를 사용해보도록 하자

2.3 TablePlus

tableplus는 db에 쉽게 접근하고 관리, 제어하도록해주는 gui툴로 어떤 db를 사용하던 간에 편하게 사용이 가능하다.

https://tableplus.com/

위 공식 홈페이지에서 다운로드하도록 하자

다운로드를 받고 실행하면 다음과 같은 화면이 나온다. 아래에 connect 부분을 눌러보자

postgresSQL database server와 연결을 해야하므로 postgreSQL을 누르자

database configuration 설정이다. 우리는 localhost이므로 127.0.0.1이 맞고 port는 5432, 유저 이름은 root이다. 또한 Database이름은 root이다. 이는 컨테이너를 만들 때 환경 변수로 설정이 가능한데 default로 설정을 하지않으면 유저이름과 같은 db를 만든다. 아래에 test 버튼을 누르면 위 사진과 같이 초록색으로 잘 연결되었다고 확인이 된다. connect 버튼을 누르면 다음의 화면이 나온다.

이제 제대로 연결된 것을 확인할 수 있다. 여기에 우리의 query 문을 넣고 실행하면 된다. 우리의 query문은 이전에 dbdiagram.io에서 만든 파일의 코드를 넣어주면 된다.

query를 넣고 중단에 있는 Run ...버튼을 누르면 성공 로그가 나오고 실행이 된다.

command + r 버튼을 누르면 refresh가 되고 새 테이블이 생긴 것을 확인할 수 있다.

3. How to write and run database migration in golang

어플리케이션을 만들고, 개발이 쭉 진행되는 동안 새로운 비지니스 로직이 추가됨에 따라 db schema는 언제든 변경이 될 수 있다. 이러한 상황을 유연하게 대처하기위해 db migration 스크립트를 만들어보고 테스트해보도록 하자

https://github.com/golang-migrate/migrate

go로 쓰여진 migration 툴이다. postgre뿐만 아니라 다양한 db에서도 사용 가능하다.

https://github.com/golang-migrate/migrate/tree/master/cmd/migrate

다음의 페이지에서 설치와 command 사용법을 익힐 수 있다.

필자는 mac 이기 때문에 brew를 통해 설치하도록 하겠다.

brew install golang-migrate
  • usage
$ migrate -help
Usage: migrate OPTIONS COMMAND [arg...]
       migrate [ -version | -help ]

Options:
  -source          Location of the migrations (driver://url)
  -path            Shorthand for -source=file://path
  -database        Run migrations against this database (driver://url)
  -prefetch N      Number of migrations to load in advance before executing (default 10)
  -lock-timeout N  Allow N seconds to acquire database lock (default 15)
  -verbose         Print verbose logging
  -version         Print version
  -help            Print usage

Commands:
  create [-ext E] [-dir D] [-seq] [-digits N] [-format] NAME
               Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.
               Use -seq option to generate sequential up/down migrations with N digits.
               Use -format option to specify a Go time format string.
  goto V       Migrate to version V
  up [N]       Apply all or N up migrations
  down [N]     Apply all or N down migrations
  drop         Drop everything inside database
  force V      Set version V but don't run migration (ignores dirty state)
  version      Print current migration version

migrate의 command 사용법은 다음과 같다.

  1. create : 새로운 migration 파일을 만들 수 있다.
  2. goto : 특정 migration 버전으로 건너뛰어 db상태를 변경할 수 있다.
  3. up, down : 순차적으로 이루어진 migration 과정에서 n번 버전 업 또는 다운을 할 수 있다.

나머진 자주 쓰이진 않는다.

migration 파일을 만들기위해 프로젝트 폴더를 만들어 가보도록 하자

mkdir simplebank
cd simplebank
mkdir -p db/migration

이제 migration 파일을 만들어보자

migrate create -ext sql -dir db/migration -seq init_schema

-ext는 extension이라는 의미로, 우리는 sql 종류 db를 사용할 것이므로 -ext sql이라고 써준다. -seq는 migration 버전 카운팅을 할 때 마지막 이름으로 어떤 것을 쓸 것이라냐는 것을 의미한다.

위 명령어를 쳐주면 simplebank/db/migration에 두 파일이 생긴다.

000001_init_schema.down.sql
000001_init_schema.up.sql

up, down 파일 하나씩 생성되었는데 왜 이렇게 생성되었냐하면 db migration 과정은 다음과 같기 때문이다.

  • migrate up
old db schema -> x1.up.sql -> new db schema

새로운 db schema를 도입하기 위해서는 migrate up을 해야하고 이 때 사용되는 기준이 바로 x1.up.sql 파일이다. 이것이 바로 위에 생성된 파일이다. 반대로 다시되돌리기(revert)하고 싶다면 down 파일을 사용하면 된다.

  • migrate down
old db schema <- x1.down.sql <- new db schema 

반대로 버전을 다운하기위해서는 down파일이 사용된다.

  • 일반적인 상황
    일반적인 상황은 순차적인 up file이 쭉 있고, 이를 순서로 버전이 업그레이드 된다.
old db schema -> x1.up.sql -> x2.up.sql -> x3.up.sql -> new db schema

반대로 버전을 다운할 때는 역순으로 간다.

old db schema <- x1.up.sql <- x2.up.sql <- x3.up.sql <- new db schema

이제 000001_init_schema.up.sql 파일에 우리가 처음 sql query 문을 생성한 코드를 넣어주도록하자

-000001_init_schema.up

CREATE TABLE "accounts" (
  "id" bigserial PRIMARY KEY,
  "owner" varchar NOT NULL,
  "currency" varchar NOT NULL,
  "balance" bigint NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

CREATE TABLE "entries" (
  "id" bigserial PRIMARY KEY,
  "account_id" bigint NOT NULL,
  "amount" bigint NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

CREATE TABLE "transfers" (
  "id" bigserial PRIMARY KEY,
  "from_account_id" bigint NOT NULL,
  "to_account_id" bigint NOT NULL,
  "amount" bigint NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

ALTER TABLE "entries" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id");

ALTER TABLE "transfers" ADD FOREIGN KEY ("from_account_id") REFERENCES "accounts" ("id");

ALTER TABLE "transfers" ADD FOREIGN KEY ("to_account_id") REFERENCES "accounts" ("id");

CREATE INDEX ON "accounts" ("owner");

CREATE INDEX ON "entries" ("account_id");

CREATE INDEX ON "transfers" ("from_account_id");

CREATE INDEX ON "transfers" ("to_account_id");

CREATE INDEX ON "transfers" ("from_account_id", "to_account_id");

COMMENT ON COLUMN "entries"."amount" IS 'negative or positive';

COMMENT ON COLUMN "transfers"."amount" IS 'negative or positive';

이제 migration 파일을 통해 자동으로 schema를 생성하고 관리할 수 있게 된 것이다.

반대로 down 파일은 테이블을 drop하도록 해주면 된다.

  • 000001_init_schema.down.sql
DROP TABLE IF EXISTS entries;
DROP TABLE IF EXISTS transfers;
DROP TABLE IF EXISTS accounts;

이제 migration script가 준비되어있다.

make file을 만들어서 사용해보도록 하자,

  • simplebank/Makefile
postgres:
	docker run --name postgres12 -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:12-alpine
createdb:
	docker exec -it postgres12 createdb --username=root --owner=root simple_bank

dropdb:
	docker exec -it postgres12 dropdb simple_bank

.PHONY: postgres createdb dropdb

다음의 명령어를 실행하기 이전에 postgres container를 종료하고 없애도록 하자

docker stop postgres12

먼저 실행중인 컨테이너를 종료하고

docker rm postgres12

컨테이너를 완전히 삭제하도록 하자

이제 Makefile에 있는 postgres명령어를 실행하도록 하자, 이를 통해 우리는 다시 컨테이너를 만들고 실행할 수 있다.

make postgres

이전에 컨테이너를 실행했던 커맨드와 같았기 때문에 특별한 것은 없다.

이제 새로 생성된 컨테이너에 db를 생성해도록 하자

make createdb

database가 잘 생성되었는 지 확인해보도록 하자, 이전처럼 TablePlus로 가자

데이터베이스 버튼을 눌러주면

다음의 창이 나오고 우리가 만든 db인 simple_bank가 나온다. 이를 눌러 확인해주면

추가된 모습을 볼 수 있다.

createdb:
	docker exec -it postgres12 createdb --username=root --owner=root simple_bank

우리가 만든 createdb스크립트는 다음과 같다.

docker exec을 통해 도커 컨테이너인 postgres12에 접속한다. 이 컨테이너에서는 postgres를 편리하게 사용하기위해 미리 만들어진 스크립트가 있는데, 그것이 바로 createdb이다. 이 내장 스크립트는 database를 만들는 기능을 하며 우리는 simple_bank라는 데이터 베이스를 만든 것이다. Makefile의 dropdb에 있는 dropdb command도 postgres 컨테이너에 기본 내장한 명령어이다.

이제 database는 만들어졌으니 테이블을 만들도록 하자, 이는 000001_init_schema.up.sql을 적용하면 된다.

이를 적용하기 위해서는 다음의 명령어를 사용하면 된다.

migrate -path <dir> -database <driver://url> -verbose up

-path는 migration 파일이 있는 위치를 의미하고, -database 어떤 데이터 베이스에 적용할 지 적어주면 된다. -verbose는 로깅을 위해 사용하고, 마지막에 up 인지 down인지를 적어주면 된다.

우리의 경우 다음과 같다.

migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable" -verbose up

다음의 명령어를 입력하면 마이그레이션 up이 완료될 것이다. 참고로 sslmode=disable을 붙이지 않으면 ssl 오류가 발생한다.

다음과 같이 테이블이 만들어지고 schema_migrations안에는 versiondirty가 있다. version은 가장 최근에 업데이트한 마이그레이션 버전을 의미하고, dirty는 마지막 마이그레이션이 실패했는 지를 알려준다. 만약 FALSE라면 성공한 것이므로 문제가 없다.

매번 이렇게 복잡한 명령어를 써줄 수는 없으니 Makefile에 migrationup, migrationdown 명령어를 추가해주도록 하자

  • Makefile
migrationup:
	migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable" -verbose up

migrationdown:
	migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable" -verbose down

.PHONY: postgres createdb dropdb migrationup migrationdown

명령어를 추가한다음 이제, make migrationdown 후에 TablePlus를 확인해보자

다음과 같은 상태가 될 것이다. 이제 다시make migrationup를 입력하여 원상복구해놓자

0개의 댓글