Skip to content

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Go Web Application Server

Go μ–Έμ–΄λ‘œ κ΅¬μΆ•ν•œ RESTful API μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μž…λ‹ˆλ‹€. Clean Architectureλ₯Ό μ μš©ν•˜μ—¬ μœ μ§€λ³΄μˆ˜μ„±κ³Ό ν…ŒμŠ€νŠΈ μš©μ΄μ„±μ„ ν™•λ³΄ν–ˆμŠ΅λ‹ˆλ‹€.

λͺ©μ°¨


기술 μŠ€νƒ

핡심 라이브러리

라이브러리 μš©λ„ 선택 이유
Gin HTTP μ›Ή ν”„λ ˆμž„μ›Œν¬ λΉ λ₯Έ μ„±λŠ₯, 미듀웨어 지원, ν’λΆ€ν•œ μƒνƒœκ³„
ent ORM (λ°μ΄ν„°λ² μ΄μŠ€) νƒ€μž… μ•ˆμ „μ„±, μ½”λ“œ 생성, 관계 관리 용이
zerolog κ΅¬μ‘°ν™”λœ λ‘œκΉ… κ³ μ„±λŠ₯, JSON 좜λ ₯, 제둜 ν• λ‹Ή
swag Swagger λ¬Έμ„œ 생성 주석 기반 μžλ™ 생성, Gin 톡합
godotenv ν™˜κ²½ λ³€μˆ˜ 관리 .env 파일 λ‘œλ“œ, μ„€μ • 뢄리
valkey-go μΊμ‹œ ν΄λΌμ΄μ–ΈνŠΈ Redis ν˜Έν™˜, κ³ μ„±λŠ₯

인프라

μ„œλΉ„μŠ€ μš©λ„ 포트
PostgreSQL 16 메인 λ°μ΄ν„°λ² μ΄μŠ€ 5433
Valkey 8 μΊμ‹œ (Redis ν˜Έν™˜) 6380

μ•„ν‚€ν…μ²˜

Clean Architecture

이 ν”„λ‘œμ νŠΈλŠ” Clean Architectureλ₯Ό μ μš©ν–ˆμŠ΅λ‹ˆλ‹€. 각 계측은 λͺ…ν™•ν•œ μ±…μž„μ„ κ°€μ§€λ©°, μ˜μ‘΄μ„±μ€ 항상 μ•ˆμͺ½(도메인)을 ν–₯ν•©λ‹ˆλ‹€.

graph TB
    subgraph "μ™ΈλΆ€ 계측"
        A[HTTP Request] --> B[Middleware]
        B --> C[Handler]
    end

    subgraph "μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 계측"
        C --> D[Usecase]
    end

    subgraph "인프라 계측"
        D --> E[Repository]
        E --> F[ent Client]
        F --> G[(PostgreSQL)]
    end

    subgraph "도메인 계측"
        H[DTO / Entity]
    end

    D -.-> H
    E -.-> H
    C -.-> H
Loading

계측별 μ—­ν• 

flowchart LR
    subgraph Handler["Handler (Presentation)"]
        direction TB
        H1[HTTP μš”μ²­/응닡 처리]
        H2[μž…λ ₯ μœ νš¨μ„± 검사]
        H3[DTO λ³€ν™˜]
    end

    subgraph Usecase["Usecase (Business Logic)"]
        direction TB
        U1[λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™ 적용]
        U2[νŠΈλžœμž­μ…˜ 관리]
        U3[μ—λŸ¬ 처리]
    end

    subgraph Repository["Repository (Data Access)"]
        direction TB
        R1[데이터 CRUD]
        R2[쿼리 λΉŒλ”]
        R3[μΊμ‹œ 처리]
    end

    Handler --> Usecase --> Repository
Loading

μ™œ Clean Architecture인가?

μž₯점 μ„€λͺ…
ν…ŒμŠ€νŠΈ μš©μ΄μ„± 각 계측을 λ…λ¦½μ μœΌλ‘œ ν…ŒμŠ€νŠΈ κ°€λŠ₯ (Mock μ£Όμž…)
μœ μ§€λ³΄μˆ˜μ„± λ³€κ²½ 영ν–₯ λ²”μœ„κ°€ ν•΄λ‹Ή 계측에 κ΅­ν•œλ¨
ν™•μž₯μ„± μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œ μˆ˜μ • μ΅œμ†Œν™”
μ˜μ‘΄μ„± μ—­μ „ μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 κ΅¬ν˜„μ²΄ ꡐ체 용이

μš”μ²­ 흐름 μ˜ˆμ‹œ

μ‚¬μš©μž 생성 μš”μ²­μ˜ 전체 흐름:

sequenceDiagram
    participant Client
    participant Middleware
    participant Handler
    participant Usecase
    participant Repository
    participant Database

    Client->>Middleware: POST /api/v1/users
    Middleware->>Middleware: Trace ID 생성
    Middleware->>Middleware: λ‘œκΉ… μ‹œμž‘
    Middleware->>Handler: μš”μ²­ 전달

    Handler->>Handler: JSON 바인딩 & μœ νš¨μ„± 검사
    Handler->>Usecase: Create(ctx, CreateRequest)

    Usecase->>Repository: GetByEmail(ctx, email)
    Repository->>Database: SELECT * FROM users WHERE email = ?
    Database-->>Repository: κ²°κ³Ό (μ—†μŒ)
    Repository-->>Usecase: nil

    Usecase->>Usecase: λΉ„λ°€λ²ˆν˜Έ ν•΄μ‹±
    Usecase->>Repository: Create(ctx, name, email, password)
    Repository->>Database: INSERT INTO users ...
    Database-->>Repository: μƒμ„±λœ User
    Repository-->>Usecase: *ent.User

    Usecase->>Usecase: DTO λ³€ν™˜
    Usecase-->>Handler: *user.Response

    Handler-->>Middleware: JSON 응닡
    Middleware->>Middleware: λ‘œκΉ… μ™„λ£Œ (latency 포함)
    Middleware-->>Client: 201 Created + X-Response-Time
Loading

ν”„λ‘œμ νŠΈ ꡬ쑰

go-webserver/
β”œβ”€β”€ cmd/
β”‚   └── server/
β”‚       └── main.go              # μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ§„μž…μ 
β”œβ”€β”€ internal/                    # λΉ„κ³΅κ°œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œ
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── config.go            # ν™˜κ²½ μ„€μ • λ‘œλ“œ
β”‚   β”œβ”€β”€ domain/                  # 도메인 DTO
β”‚   β”‚   β”œβ”€β”€ user/
β”‚   β”‚   β”‚   └── dto.go           # μ‚¬μš©μž μš”μ²­/응닡 DTO
β”‚   β”‚   β”œβ”€β”€ product/
β”‚   β”‚   β”‚   └── dto.go
β”‚   β”‚   └── order/
β”‚   β”‚       └── dto.go
β”‚   β”œβ”€β”€ handler/                 # HTTP ν•Έλ“€λŸ¬ (Controller)
β”‚   β”‚   β”œβ”€β”€ router.go            # λΌμš°ν„° μ„€μ •
β”‚   β”‚   β”œβ”€β”€ user_handler.go
β”‚   β”‚   β”œβ”€β”€ product_handler.go
β”‚   β”‚   β”œβ”€β”€ order_handler.go
β”‚   β”‚   └── health_handler.go
β”‚   β”œβ”€β”€ usecase/                 # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직
β”‚   β”‚   β”œβ”€β”€ user_usecase.go
β”‚   β”‚   β”œβ”€β”€ product_usecase.go
β”‚   β”‚   └── order_usecase.go
β”‚   β”œβ”€β”€ repository/              # 데이터 μ ‘κ·Ό 계측
β”‚   β”‚   β”œβ”€β”€ user_repository.go
β”‚   β”‚   β”œβ”€β”€ product_repository.go
β”‚   β”‚   └── order_repository.go
β”‚   β”œβ”€β”€ middleware/              # HTTP 미듀웨어
β”‚   β”‚   β”œβ”€β”€ logger.go            # μš”μ²­ λ‘œκΉ… + Latency
β”‚   β”‚   β”œβ”€β”€ trace.go             # Trace ID 생성
β”‚   β”‚   β”œβ”€β”€ cors.go              # CORS μ„€μ •
β”‚   β”‚   └── recovery.go          # νŒ¨λ‹‰ 볡ꡬ
β”‚   └── infrastructure/          # μ™ΈλΆ€ μ„œλΉ„μŠ€ μ—°κ²°
β”‚       β”œβ”€β”€ database/
β”‚       β”‚   └── database.go      # PostgreSQL μ—°κ²°
β”‚       β”œβ”€β”€ cache/
β”‚       β”‚   └── cache.go         # Valkey μ—°κ²°
β”‚       └── logger/
β”‚           └── logger.go        # zerolog μ„€μ •
β”œβ”€β”€ ent/                         # ent ORM (μžλ™ 생성)
β”‚   β”œβ”€β”€ schema/                  # μŠ€ν‚€λ§ˆ μ •μ˜ (직접 μž‘μ„±)
β”‚   β”‚   β”œβ”€β”€ user.go
β”‚   β”‚   β”œβ”€β”€ product.go
β”‚   β”‚   β”œβ”€β”€ order.go
β”‚   β”‚   └── orderitem.go
β”‚   └── migrate/
β”‚       └── main.go              # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 생성기
β”œβ”€β”€ migrations/                  # SQL λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일
β”œβ”€β”€ docs/                        # Swagger λ¬Έμ„œ (μžλ™ 생성)
β”œβ”€β”€ .air.toml                    # Hot reload μ„€μ •
β”œβ”€β”€ .env                         # ν™˜κ²½ λ³€μˆ˜
β”œβ”€β”€ docker-compose.yml           # Docker μ„€μ •
β”œβ”€β”€ Makefile                     # λΉŒλ“œ 슀크립트
└── go.mod                       # Go λͺ¨λ“ˆ μ •μ˜

디렉토리 κ·œμΉ™

디렉토리 κ·œμΉ™
cmd/ μ‹€ν–‰ νŒŒμΌλ³„ μ§„μž…μ . μ΅œμ†Œν•œμ˜ μ½”λ“œλ§Œ 포함
internal/ μ™ΈλΆ€ νŒ¨ν‚€μ§€μ—μ„œ import λΆˆκ°€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ „μš© μ½”λ“œ
ent/schema/ 직접 μž‘μ„±. λ‚˜λ¨Έμ§€ ent μ½”λ“œλŠ” μžλ™ 생성
docs/ swag λͺ…λ ΉμœΌλ‘œ μžλ™ 생성. 직접 μˆ˜μ • κΈˆμ§€

μ‹œμž‘ν•˜κΈ°

ν•„μˆ˜ 도ꡬ

  • Go 1.21+
  • Docker & Docker Compose
  • Make

μ„€μΉ˜ 및 μ‹€ν–‰

# 1. μ €μž₯μ†Œ 클둠
git clone <repository-url>
cd go-webserver

# 2. ν™˜κ²½ λ³€μˆ˜ μ„€μ •
cp .env.example .env  # λ˜λŠ” κΈ°μ‘΄ .env μ‚¬μš©

# 3. Docker μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ (PostgreSQL, Valkey)
make docker-up

# 4. μ˜μ‘΄μ„± μ„€μΉ˜ 및 μ½”λ“œ 생성
make setup

# 5. λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용
make migrate-up

# 6. μ„œλ²„ μ‹€ν–‰
make run

개발 λͺ¨λ“œ (Hot Reload)

# air μ„€μΉ˜ (졜초 1회)
go install github.com/air-verse/air@latest

# 개발 μ„œλ²„ μ‹€ν–‰
make dev
# λ˜λŠ”
make air

μ£Όμš” Make λͺ…λ Ήμ–΄

make build          # λ°”μ΄λ„ˆλ¦¬ λΉŒλ“œ
make run            # μ„œλ²„ μ‹€ν–‰
make dev            # 개발 λͺ¨λ“œ (hot reload)
make test           # ν…ŒμŠ€νŠΈ μ‹€ν–‰
make test-coverage  # ν…ŒμŠ€νŠΈ 컀버리지 리포트

make docker-up      # Docker μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘
make docker-down    # Docker μ»¨ν…Œμ΄λ„ˆ μ’…λ£Œ
make docker-logs    # Docker 둜그 확인

make generate       # ent μ½”λ“œ 생성
make swagger        # Swagger λ¬Έμ„œ 생성
make lint           # μ½”λ“œ 린트

make migrate name=init    # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일 생성
make migrate-up           # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용
make migrate-down         # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ λ‘€λ°±
make migrate-status       # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μƒνƒœ 확인

API λ¬Έμ„œ

μ„œλ²„ μ‹€ν–‰ ν›„ Swagger UIμ—μ„œ APIλ₯Ό ν™•μΈν•˜κ³  ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€:

http://localhost:8080/swagger/index.html

μ£Όμš” μ—”λ“œν¬μΈνŠΈ

λ©”μ„œλ“œ 경둜 μ„€λͺ…
GET /api/v1/health ν—¬μŠ€ 체크
Users
POST /api/v1/users μ‚¬μš©μž 생성
GET /api/v1/users μ‚¬μš©μž λͺ©λ‘ 쑰회
GET /api/v1/users/:id μ‚¬μš©μž 상세 쑰회
PUT /api/v1/users/:id μ‚¬μš©μž μˆ˜μ •
DELETE /api/v1/users/:id μ‚¬μš©μž μ‚­μ œ
Products
POST /api/v1/products μƒν’ˆ 생성
GET /api/v1/products μƒν’ˆ λͺ©λ‘ 쑰회
GET /api/v1/products/:id μƒν’ˆ 상세 쑰회
PUT /api/v1/products/:id μƒν’ˆ μˆ˜μ •
DELETE /api/v1/products/:id μƒν’ˆ μ‚­μ œ
Orders
POST /api/v1/orders μ£Όλ¬Έ 생성
GET /api/v1/orders/:id μ£Όλ¬Έ 상세 쑰회
PATCH /api/v1/orders/:id/status μ£Όλ¬Έ μƒνƒœ λ³€κ²½
DELETE /api/v1/orders/:id μ£Όλ¬Έ μ‚­μ œ
GET /api/v1/users/:id/orders μ‚¬μš©μžλ³„ μ£Όλ¬Έ 쑰회

개발 κ°€μ΄λ“œ

μƒˆλ‘œμš΄ 도메인 μΆ”κ°€ν•˜κΈ°

  1. ent μŠ€ν‚€λ§ˆ μ •μ˜ (ent/schema/)
// ent/schema/category.go
package schema

import (
    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

type Category struct {
    ent.Schema
}

func (Category) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").NotEmpty(),
        field.String("description").Optional(),
    }
}
  1. ent μ½”λ“œ 생성
make generate
  1. DTO μ •μ˜ (internal/domain/category/)
// internal/domain/category/dto.go
package category

type CreateRequest struct {
    Name        string `json:"name" binding:"required"`
    Description string `json:"description"`
}

type Response struct {
    ID          int    `json:"id"`
    Name        string `json:"name"`
    Description string `json:"description"`
}
  1. Repository μž‘μ„± (internal/repository/)

  2. Usecase μž‘μ„± (internal/usecase/)

  3. Handler μž‘μ„± (internal/handler/)

  4. λΌμš°ν„° 등둝 (internal/handler/router.go)

  5. Swagger λ¬Έμ„œ 생성

make swagger

Context μ „νŒŒ

Goμ—μ„œλŠ” context.Contextλ₯Ό λͺ¨λ“  계측에 λͺ…μ‹œμ μœΌλ‘œ μ „λ‹¬ν•©λ‹ˆλ‹€:

// Handler
func (h *UserHandler) GetByID(c *gin.Context) {
    ctx := c.Request.Context()  // gin.Contextμ—μ„œ μΆ”μΆœ
    user, err := h.usecase.GetByID(ctx, id)
    // ...
}

// Usecase
func (u *userUsecase) GetByID(ctx context.Context, id int) (*user.Response, error) {
    found, err := u.repo.GetByID(ctx, id)
    // ...
}

// Repository
func (r *userRepository) GetByID(ctx context.Context, id int) (*ent.User, error) {
    return r.client.User.Get(ctx, id)  // ent에 전달
}

μ΄λ ‡κ²Œ ν•˜λ©΄:

  • Trace IDκ°€ λͺ¨λ“  λ‘œκ·Έμ— μžλ™ 포함
  • μš”μ²­ μ·¨μ†Œκ°€ DB μΏΌλ¦¬κΉŒμ§€ μ „νŒŒ
  • νƒ€μž„μ•„μ›ƒ μ„€μ • κ°€λŠ₯

λ‘œκΉ…

import "go-webserver/internal/infrastructure/logger"

// κΈ°λ³Έ λ‘œκΉ…
logger.Info().Str("key", "value").Msg("λ©”μ‹œμ§€")

// Context 기반 λ‘œκΉ… (Trace ID μžλ™ 포함)
logger.Ctx(ctx).Debug().Interface("user", user).Msg("μ‚¬μš©μž 쑰회")

// 둜그 레벨
logger.Debug()  // 개발용 상세 정보
logger.Info()   // 일반 정보
logger.Warn()   // κ²½κ³ 
logger.Error()  // μ—λŸ¬
logger.Fatal()  // 치λͺ…적 μ—λŸ¬ (ν”„λ‘œκ·Έλž¨ μ’…λ£Œ)

ν…ŒμŠ€νŠΈ

// usecase ν…ŒμŠ€νŠΈ μ˜ˆμ‹œ
func TestUserUsecase_Create(t *testing.T) {
    // Mock repository μ„€μ •
    mockRepo := new(MockUserRepository)
    usecase := NewUserUsecase(mockRepo)

    // Given
    req := user.CreateRequest{
        Name:     "ν…ŒμŠ€νŠΈ",
        Email:    "test@example.com",
        Password: "password123",
    }

    // When
    result, err := usecase.Create(context.Background(), req)

    // Then
    assert.NoError(t, err)
    assert.Equal(t, "ν…ŒμŠ€νŠΈ", result.Name)
}

λ§ˆμ΄κ·Έλ ˆμ΄μ…˜

이 ν”„λ‘œμ νŠΈλŠ” golang-migrateλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일 생성

# ent μŠ€ν‚€λ§ˆλ₯Ό 기반으둜 SQL 생성
make migrate name=add_categories

μƒμ„±λ˜λŠ” 파일:

  • migrations/20240118120000_add_categories.up.sql
  • migrations/20240118120000_add_categories.down.sql

λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용

# μƒˆλ‘œμš΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜λ§Œ 적용 (이미 적용된 것은 μŠ€ν‚΅)
make migrate-up

# 1단계 λ‘€λ°±
make migrate-down

# ν˜„μž¬ 버전 확인
make migrate-status

λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ ν…Œμ΄λΈ”

schema_migrations ν…Œμ΄λΈ”μ—μ„œ 적용된 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ„ μΆ”μ ν•©λ‹ˆλ‹€:

SELECT * FROM schema_migrations;
-- version | dirty
-- 20240118120000 | false

응닡 헀더

λͺ¨λ“  API 응닡에 λ‹€μŒ 헀더가 ν¬ν•¨λ©λ‹ˆλ‹€:

헀더 μ„€λͺ… μ˜ˆμ‹œ
X-Trace-ID μš”μ²­ 좔적 ID a838ac04
X-Response-Time 응닡 μ‹œκ°„ (ms) 1.234

λΌμ΄μ„ μŠ€

MIT License

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages