Skip to content

Commit

Permalink
Feat: Section 75
Browse files Browse the repository at this point in the history
  • Loading branch information
gleaming9 committed Nov 18, 2024
1 parent 16acda1 commit df4131e
Show file tree
Hide file tree
Showing 40 changed files with 1,161 additions and 68 deletions.
43 changes: 43 additions & 0 deletions _chapter19/section75/.air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
root = '.'
tmp_dir = 'tmp'

[build]
cmd = "go build -o ./tmp/main ." #Go 프로젝트를 빌드
bin = "./tmp/main" #빌드된 실행 파일 경로
#실행 파일 실행 시 필요한 환경 변수와 인자 설정 -> 80번 포트 사용하도록 인수 지정
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main 80"

# 파일 변경 감지 설정 -> 변경을 감지할 파일 확장자
include_ext = ["go", "tpl", "tmpl", "html"]
# 감지하지 않을 디렉터리 목록
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules", "_tools", "cert", "testutil"]
include_dir = []
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = true
exclude_underscore = false
follow_symlink = false

#로깅 설정 : 로그파일 경로, 파일 변경 감지 후 재빌드 대기 시간
log = "air.log"
delay = 1000

#오류 처리 설정
stop_on_error = true
send_interrupt = false
kill_delay = 500

[log]
time = false

[color]
#로그 출력 시 사용할 색상 설정
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

#프로그램 종료 시 임시 디렉터리 삭제 여부
[misc]
clean_on_exit = true

2 changes: 2 additions & 0 deletions _chapter19/section75/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.git
.DS_Store
43 changes: 43 additions & 0 deletions _chapter19/section75/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#최종 배포할 실행 파일 만드는 과정
#multi-stage build에서 이 단계의 이름을 'deploy-builder'로 지정
FROM golang:1.23.1-bullseye AS deploy-builder

#app 디렉토리 생성 및 작업 디렉토리로 설정
WORKDIR /app

#종속성 파일(go.mod go.sum)만 복사해서 캐시 활용
#소스코드가 변경되어도 종속성이 변경되지 않았다면 도커의 캐시 활용
COPY go.mod go.sum ./
#프로젝트의 모든 종속성 다운로드
RUN go mod download

#전체 소스 파일을 /app으로 복사하고 최적화된 바이너리 생성
COPY . .
RUN go build -trimpath -ldflags="-w -s" -o app

# 최종 배포 단계 : 실제 운영 환경에서 실행될 최소한의 이미지 생성
#경량화된 Degian 이미지 사용
FROM debian:bullseye-slim AS deploy

#시스템 패키지 최신화
RUN apt-get update

#첫 번째 단계에서 만든 실행 파일만 복사
COPY --from=deploy-builder /app/app .

#컨테이너 시작 시 애플리케이션 자동 실행
CMD ["./app"]

#개발자의 로컬 환경을 위한 설정
FROM golang:1.23 AS dev

#/app 디렉토리를 작업공간으로 설정
WORKDIR /app

#air 도구 설치 (코드 변경 시 자동 재빌드 지원)
RUN go install github.com/air-verse/air@latest

#개발 서버 자동 시작
CMD ["air"]

## docker-compose down -v <= 실행중인 컨테이너를 멈추고 매핑된 볼륨들을 제거
35 changes: 35 additions & 0 deletions _chapter19/section75/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.PHONY: help build build-local up down logs ps test
.DEFAULT_GOAL := help

DOCKER_TAG := latest
build: ## Build the docker image
docker build -t gleaming9/go_todo_app:${DOCKER_TAG} \
--target deploy ./

build-local: ## Build the docker image for local development
docker compose build --no-cache

up: ## 자동 새로고침을 사용한 도커 컴포즈 실행
docker compose up -d

down: ## 도커 컴포즈 종료
docker compose down

logs: ## 도커 컴포즈 로그 출력
docker compose logs -f

ps: ## 실행중인 컨테이너 확인
docker compose ps

test: ## 테스트 실행
go test -race -shuffle=on ./...

dry-migrate: ## 마이그레이션 실행 전 미리보기
mysqldef -u todo -p todo -h 127.0.0.1 -P 33306 todo --dry-run < ./_tools/mysql/schema.sql

migrate: ## 마이그레이션 실행
mysqldef -u todo -p todo -h 127.0.0.1 -P 33306 todo < ./_tools/mysql/schema.sql

help: ## 옵션 보기
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
2 changes: 2 additions & 0 deletions _chapter19/section75/_tools/mysql/conf.d/mysql.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mysql]
default_character_set=utf8mb4
4 changes: 4 additions & 0 deletions _chapter19/section75/_tools/mysql/conf.d/mysqld.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[mysqld]
default-authentication-plugin=mysql_native_password
charater_set_server=utf8mb4
sql_mode=TRADIIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY
21 changes: 21 additions & 0 deletions _chapter19/section75/_tools/mysql/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CREATE TABLE `user`
(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '사용자 식별자',
`name` varchar(20) NOT NULL COMMENT '사용자명',
`password` VARCHAR(80) NOT NULL COMMENT '패스워드 해시',
`role` VARCHAR(80) NOT NULL COMMENT '역할',
`created` DATETIME(6) NOT NULL COMMENT '레코드 작성 시간',
`modified` DATETIME(6) NOT NULL COMMENT '레코드 수정 시간',
PRIMARY KEY (`id`),
UNIQUE KEY `uix_name` (`name`) USING BTREE
) Engine=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='사용자';

CREATE TABLE `task`
(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '태스크 식별자',
`title` VARCHAR(128) NOT NULL COMMENT '태스크 타이틀',
`status` VARCHAR(20) NOT NULL COMMENT '태스크 상태',
`created` DATETIME(6) NOT NULL COMMENT '레코드 작성 시간',
`modified` DATETIME(6) NOT NULL COMMENT '레코드 수정 시간',
PRIMARY KEY (`id`)
) Engine=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='태스크';
19 changes: 19 additions & 0 deletions _chapter19/section75/clock/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package clock

import "time"

type Clocker interface {
Now() time.Time
}

type RealClocker struct{}

func (r RealClocker) Now() time.Time {
return time.Now()
}

type FixedClocker struct{}

func (fc FixedClocker) Now() time.Time {
return time.Date(2022, 5, 10, 12, 34, 56, 0, time.UTC)
}
24 changes: 24 additions & 0 deletions _chapter19/section75/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package config

import "github.com/caarlos0/env/v6"

// Config 구조체는 애플리케이션 설정을 위한 구조체이다.
// 환경 변수에서 값을 가져와서 필드를 초기화한다.
type Config struct {
Env string `env:"TODO_ENV" envDefault:"dev"`
Port int `env:"PORT" envDefault:"80"`
DBHost string `env:"TODO_DB_HOST" envDefault:"127.0.0.1"`
DBPort int `env:"TODO_DB_PORT" envDefault:"33306"`
DBUser string `env:"TODO_DB_USER" envDefault:"todo"`
DBPassword string `env:"TODO_DB_PASSWORD" envDefault:"todo"`
DBName string `env:"TODO_DB_NAME" envDefault:"todo"`
}

// New 함수는 Config 구조체를 생성하고 환경 변수를 파싱하여 필드를 초기화
func New() (*Config, error) {
cfg := &Config{} // config 구조체 초기화
if err := env.Parse(cfg); err != nil {
return nil, err
}
return cfg, nil
}
23 changes: 23 additions & 0 deletions _chapter19/section75/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package config

import (
"fmt"
"testing"
)

func TestNeow(t *testing.T) {
wantPort := 3333
t.Setenv("PORT", fmt.Sprint(wantPort))

got, err := New()
if err != nil {
t.Fatalf("cannot create config: %v", err)
}
if got.Port != wantPort {
t.Errorf("want %d, but %d", wantPort, got.Port)
}
wantEnv := "dev"
if got.Env != wantEnv {
t.Errorf("want %s, but %s", wantEnv, got.Env)
}
}
49 changes: 49 additions & 0 deletions _chapter19/section75/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
services:

# 'app'이라는 이름의 서비스(컨테이너)를 정의
app:
# 사용할 Docker 이미지 이름 : 'gotodo'
image: gotodo

build:
#빌드할 파일들이 있는 위치를 지정 : .. <- 한칸 위 폴더에 go.mod 위치
#go_todo_app 폴더를 컨텍스트로 설정
context: .
dockerfile: Dockerfile
# target=dev로 설정하여 Dockerfile의 dev stage를 빌드
args:
target: dev
environment: # 환경 변수 설정
TODO_ENV: dev
PORT: 8080
TODO_DB_HOST: todo-db
TODO_DB_PORT: 3306
TODO_DB_USER: todo
TODO_DB_PASSWORD: todo
TODO_DB_NAME: todo

# 로컬 컴퓨터의 파일과 컨테이너 안의 파일을 연결, 코드 수정 시 바로 컨테이너에 반영
volumes:
- .:/app # go_todo_app 루트 디렉터리를 컨테이너의 /app에 마운트

# 컨테이너가 사용할 포트를 지정
ports:
# 웹 브라우저에서 localhost:18000으로 접속하면 컨테이너의 80번 포트로 연결
# 왼쪽은 내 컴퓨터의 포트, 오른쪽은 컨테이너 안의 포트
- "18000:8080"
todo-db:
image: mysql:8.0.29
platform: linux/amd64
container_name: todo-db
environment:
MYSQL_ROOT_PASSWORD: "yes"
MYSQL_DATABASE: todo
MYSQL_USER: todo
MYSQL_PASSWORD: todo
volumes:
- todo-db-data:/var/lib/mysql
- ./_tools/mysql/conf.d:/etc/mysql/conf.d:cached
ports:
- "33306:3306"
volumes:
todo-db-data:
22 changes: 22 additions & 0 deletions _chapter19/section75/entity/task.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package entity

import "time"

type TaskID int64
type TaskStatus string

const (
TaskStatusTodo TaskStatus = "todo"
TaskStatusDoing TaskStatus = "doing"
TaskStatusDone TaskStatus = "done"
)

type Task struct {
ID TaskID `json:"id" db:"id"`
Title string `json:"title" db:"title"`
Status TaskStatus `json:"status" db:"status"`
Created time.Time `json:"created" db:"created"`
Modified time.Time `json:"modified" db:"modified"`
}

type Tasks []*Task
18 changes: 18 additions & 0 deletions _chapter19/section75/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module go_todo_app

go 1.23.1

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/caarlos0/env/v6 v6.10.1 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator v9.31.0+incompatible // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/sync v0.9.0 // indirect
)
15 changes: 15 additions & 0 deletions _chapter19/section75/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
Loading

0 comments on commit df4131e

Please sign in to comment.