Notice
Recent Posts
Recent Comments
Link
250x250
반응형
«   2025/10   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

백고등어 개발 블로그

왜 Google, Netflix, Uber가 모두 Go로 갈아탔을까? 초보자도 7일만에 Go 마스터하기 본문

카테고리 없음

왜 Google, Netflix, Uber가 모두 Go로 갈아탔을까? 초보자도 7일만에 Go 마스터하기

백고등어 2025. 9. 26. 17:47
728x90
반응형

"Go 언어 배워야 하나요?" 이런 질문을 받으면 저는 항상 이렇게 답합니다.

 

"지금 안 배우면 5년 후에 후회할 겁니다."

 

구글이 만든 Go 언어가 왜 Docker, Kubernetes, Prometheus를 비롯한 모든 클라우드 네이티브 도구들의 표준이 되었는지 아시나요?

대기업들이 Go를 선택하는 진짜 이유

성능: 컴파일 언어의 속도 + 스크립트 언어의 편의성

# Python 서버
$ time curl http://localhost:8000/api/users
real    0m0.145s

# Go 서버  
$ time curl http://localhost:8080/api/users
real    0m0.008s

18배 빠른 응답속도! 이것이 바로 Uber가 Node.js에서 Go로 갈아탄 이유입니다.

동시성: 고루틴의 마법

// 1만 개의 동시 작업을 가볍게 처리
func main() {
    for i := 0; i < 10000; i++ {
        go func(id int) {
            fmt.Printf("Goroutine %d is running\n", id)
            time.Sleep(time.Second)
        }(i)
    }
    
    time.Sleep(2 * time.Second)
}

자바에서 1만 개의 스레드를 만들면? 메모리 부족으로 서버 다운
Go에서 1만 개의 고루틴을 만들면? 아무것도 아님

배포: 단일 바이너리의 혁명

# 빌드
$ go build -o myapp main.go

# 배포 (의존성 걱정 없음!)
$ scp myapp user@server:/usr/local/bin/
$ ssh user@server "myapp &"

Docker, JVM, Python 런타임? 필요 없습니다. 파일 하나면 끝.

Day 1-2: Go의 첫인상, "이렇게 단순할 수가?"

설치부터 Hello World까지

# 설치 (Mac)
brew install go

# 설치 (Linux)
wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
sudo tar -xvf go1.21.0.linux-amd64.tar.gz -C /usr/local

# 설치 확인
go version
// main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
}
# 실행
$ go run main.go
Hello, Go!

# 빌드
$ go build main.go
$ ./main
Hello, Go!

변수와 타입 - 명시적이면서도 간결한

package main

import "fmt"

func main() {
    // 변수 선언 방법들
    var name string = "홍길동"
    var age int = 25
    
    // 타입 추론
    var city = "서울"
    
    // 짧은 선언 (함수 내에서만 가능)
    country := "한국"
    
    // 여러 변수 한번에
    var (
        firstName = "길동"
        lastName  = "홍"
        isActive  = true
    )
    
    fmt.Printf("이름: %s, 나이: %d, 도시: %s, 국가: %s\n", 
               name, age, city, country)
    fmt.Printf("성: %s, 이름: %s, 활성화: %t\n", 
               lastName, firstName, isActive)
}

함수 - 다중 반환값의 혁신

package main

import (
    "fmt"
    "errors"
)

// 기본 함수
func add(a, b int) int {
    return a + b
}

// 다중 반환값 - Go의 핵심 특징
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("0으로 나눌 수 없습니다")
    }
    return a / b, nil
}

// 명명된 반환값
func getUserInfo(id int) (name string, age int, err error) {
    if id <= 0 {
        err = errors.New("잘못된 사용자 ID")
        return  // name, age는 제로값으로 자동 설정
    }
    
    name = "홍길동"
    age = 25
    return  // 명시적으로 값을 써도 되고 안 써도 됨
}

func main() {
    // 기본 함수 사용
    result := add(10, 20)
    fmt.Printf("덧셈 결과: %d\n", result)
    
    // 다중 반환값 처리
    quotient, err := divide(10, 3)
    if err != nil {
        fmt.Printf("에러: %s\n", err)
    } else {
        fmt.Printf("나눗셈 결과: %.2f\n", quotient)
    }
    
    // 에러 무시하기
    quotient2, _ := divide(10, 2)
    fmt.Printf("나눗셈 결과: %.2f\n", quotient2)
    
    // 명명된 반환값 사용
    name, age, err := getUserInfo(1)
    if err != nil {
        fmt.Printf("에러: %s\n", err)
    } else {
        fmt.Printf("사용자: %s, 나이: %d\n", name, age)
    }
}

Day 3-4: 구조체와 메서드 - 객체지향의 Go 방식

구조체 정의와 사용

package main

import (
    "fmt"
    "time"
)

// 구조체 정의
type User struct {
    ID        int
    Name      string
    Email     string
    CreatedAt time.Time
    isActive  bool  // 소문자 시작 = private
}

// 생성자 함수 (관례)
func NewUser(name, email string) *User {
    return &User{
        ID:        generateID(),
        Name:      name,
        Email:     email,
        CreatedAt: time.Now(),
        isActive:  true,
    }
}

// 메서드 정의 - 리시버(receiver) 사용
func (u *User) Activate() {
    u.isActive = true
}

func (u *User) Deactivate() {
    u.isActive = false
}

func (u User) IsActive() bool {  // 값 리시버 (읽기 전용)
    return u.isActive
}

func (u User) String() string {  // Stringer 인터페이스 구현
    return fmt.Sprintf("User{ID: %d, Name: %s, Email: %s}", 
                      u.ID, u.Name, u.Email)
}

// 임베딩 (상속 대신)
type Admin struct {
    User        // User 구조체 임베딩
    Permissions []string
}

func (a *Admin) AddPermission(permission string) {
    a.Permissions = append(a.Permissions, permission)
}

var idCounter = 1

func generateID() int {
    idCounter++
    return idCounter
}

func main() {
    // 구조체 생성 방법들
    user1 := User{
        ID:    1,
        Name:  "홍길동",
        Email: "hong@example.com",
    }
    
    user2 := NewUser("김영희", "kim@example.com")
    
    // 메서드 호출
    fmt.Println("활성화 상태:", user2.IsActive())
    user2.Deactivate()
    fmt.Println("비활성화 후:", user2.IsActive())
    
    // String() 메서드 자동 호출
    fmt.Println(user1)
    fmt.Println(user2)
    
    // 임베딩 사용
    admin := &Admin{
        User: *NewUser("관리자", "admin@example.com"),
    }
    admin.AddPermission("READ")
    admin.AddPermission("WRITE")
    
    fmt.Printf("관리자: %s, 권한: %v\n", admin.Name, admin.Permissions)
}

인터페이스 - Go의 핵심 철학

package main

import (
    "fmt"
    "math"
)

// 인터페이스 정의 - 덕 타이핑
type Shape interface {
    Area() float64
    Perimeter() float64
}

// 빈 인터페이스 - 모든 타입
type Printer interface {
    Print()
}

// Circle 구조체
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

func (c Circle) Print() {
    fmt.Printf("원 - 반지름: %.2f\n", c.Radius)
}

// Rectangle 구조체  
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

func (r Rectangle) Print() {
    fmt.Printf("사각형 - 너비: %.2f, 높이: %.2f\n", r.Width, r.Height)
}

// 인터페이스를 매개변수로 받는 함수
func printShapeInfo(s Shape) {
    fmt.Printf("넓이: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}

func printAny(p Printer) {
    p.Print()
}

func main() {
    circle := Circle{Radius: 5}
    rectangle := Rectangle{Width: 10, Height: 6}
    
    // 암시적 인터페이스 구현
    printShapeInfo(circle)
    printShapeInfo(rectangle)
    
    printAny(circle)
    printAny(rectangle)
    
    // 인터페이스 슬라이스
    shapes := []Shape{circle, rectangle}
    
    for i, shape := range shapes {
        fmt.Printf("도형 %d: ", i+1)
        printShapeInfo(shape)
    }
}

Day 5-6: 동시성 프로그래밍 - Go의 진짜 파워

고루틴과 채널

package main

import (
    "fmt"
    "sync"
    "time"
)

// 기본 고루틴
func sayHello(name string) {
    for i := 0; i < 5; i++ {
        fmt.Printf("Hello %s! (%d)\n", name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

// 채널을 이용한 통신
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, job)
        time.Sleep(time.Second)  // 작업 시뮬레이션
        fmt.Printf("Worker %d finished job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    // 1. 기본 고루틴
    fmt.Println("=== 기본 고루틴 ===")
    go sayHello("Alice")
    go sayHello("Bob")
    
    time.Sleep(600 * time.Millisecond)
    
    // 2. WaitGroup 사용
    fmt.Println("\n=== WaitGroup 사용 ===")
    var wg sync.WaitGroup
    
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d is working...\n", id)
            time.Sleep(time.Second)
            fmt.Printf("Goroutine %d is done\n", id)
        }(i)
    }
    
    wg.Wait()
    fmt.Println("All goroutines finished!")
    
    // 3. 채널을 이용한 작업자 풀
    fmt.Println("\n=== Worker Pool ===")
    const numJobs = 5
    const numWorkers = 3
    
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    
    // 워커 고루틴 시작
    for w := 1; w <= numWorkers; w++ {
        go worker(w, jobs, results)
    }
    
    // 작업 전송
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 결과 수집
    for a := 1; a <= numJobs; a++ {
        result := <-results
        fmt.Printf("Result: %d\n", result)
    }
}

실전 예제: 웹 크롤러

package main

import (
    "fmt"
    "io"
    "net/http"
    "sync"
    "time"
)

type Result struct {
    URL    string
    Status int
    Size   int
    Error  error
}

func fetchURL(url string, results chan<- Result) {
    start := time.Now()
    
    resp, err := http.Get(url)
    if err != nil {
        results <- Result{URL: url, Error: err}
        return
    }
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        results <- Result{URL: url, Status: resp.StatusCode, Error: err}
        return
    }
    
    duration := time.Since(start)
    fmt.Printf("Fetched %s (%d bytes) in %v\n", url, len(body), duration)
    
    results <- Result{
        URL:    url,
        Status: resp.StatusCode,
        Size:   len(body),
        Error:  nil,
    }
}

func main() {
    urls := []string{
        "https://google.com",
        "https://github.com",
        "https://stackoverflow.com",
        "https://golang.org",
        "https://reddit.com",
    }
    
    results := make(chan Result, len(urls))
    
    // 모든 URL을 동시에 처리
    for _, url := range urls {
        go fetchURL(url, results)
    }
    
    // 결과 수집
    for i := 0; i < len(urls); i++ {
        result := <-results
        if result.Error != nil {
            fmt.Printf("ERROR %s: %v\n", result.URL, result.Error)
        } else {
            fmt.Printf("SUCCESS %s: %d (%d bytes)\n", 
                      result.URL, result.Status, result.Size)
        }
    }
}

Day 7: 실전 프로젝트 - REST API 서버

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "sync"
    "time"
)

// User 모델
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

// UserStore - 메모리 기반 저장소
type UserStore struct {
    mu    sync.RWMutex
    users map[int]*User
    nextID int
}

func NewUserStore() *UserStore {
    return &UserStore{
        users: make(map[int]*User),
        nextID: 1,
    }
}

func (s *UserStore) Create(name, email string) *User {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    user := &User{
        ID:        s.nextID,
        Name:      name,
        Email:     email,
        CreatedAt: time.Now(),
    }
    
    s.users[s.nextID] = user
    s.nextID++
    
    return user
}

func (s *UserStore) GetByID(id int) (*User, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    
    user, exists := s.users[id]
    return user, exists
}

func (s *UserStore) GetAll() []*User {
    s.mu.RLock()
    defer s.mu.RUnlock()
    
    users := make([]*User, 0, len(s.users))
    for _, user := range s.users {
        users = append(users, user)
    }
    
    return users
}

func (s *UserStore) Update(id int, name, email string) (*User, bool) {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    user, exists := s.users[id]
    if !exists {
        return nil, false
    }
    
    user.Name = name
    user.Email = email
    
    return user, true
}

func (s *UserStore) Delete(id int) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    _, exists := s.users[id]
    if exists {
        delete(s.users, id)
    }
    
    return exists
}

// HTTP 핸들러들
type UserHandler struct {
    store *UserStore
}

func NewUserHandler(store *UserStore) *UserHandler {
    return &UserHandler{store: store}
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    path := strings.TrimPrefix(r.URL.Path, "/api/users")
    
    switch {
    case path == "" || path == "/":
        h.handleUsers(w, r)
    case strings.HasPrefix(path, "/"):
        h.handleUser(w, r, path[1:])  // ID 부분 추출
    default:
        http.NotFound(w, r)
    }
}

func (h *UserHandler) handleUsers(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        h.getUsers(w, r)
    case http.MethodPost:
        h.createUser(w, r)
    default:
        w.Header().Set("Allow", "GET, POST")
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func (h *UserHandler) handleUser(w http.ResponseWriter, r *http.Request, idStr string) {
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }
    
    switch r.Method {
    case http.MethodGet:
        h.getUser(w, r, id)
    case http.MethodPut:
        h.updateUser(w, r, id)
    case http.MethodDelete:
        h.deleteUser(w, r, id)
    default:
        w.Header().Set("Allow", "GET, PUT, DELETE")
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func (h *UserHandler) getUsers(w http.ResponseWriter, r *http.Request) {
    users := h.store.GetAll()
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func (h *UserHandler) createUser(w http.ResponseWriter, r *http.Request) {
    var req struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    if req.Name == "" || req.Email == "" {
        http.Error(w, "Name and email are required", http.StatusBadRequest)
        return
    }
    
    user := h.store.Create(req.Name, req.Email)
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) getUser(w http.ResponseWriter, r *http.Request, id int) {
    user, exists := h.store.GetByID(id)
    if !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) updateUser(w http.ResponseWriter, r *http.Request, id int) {
    var req struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    user, exists := h.store.Update(id, req.Name, req.Email)
    if !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) deleteUser(w http.ResponseWriter, r *http.Request, id int) {
    if !h.store.Delete(id) {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    
    w.WriteHeader(http.StatusNoContent)
}

// 미들웨어
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // 다음 핸들러 호출
        next.ServeHTTP(w, r)
        
        duration := time.Since(start)
        log.Printf("%s %s %v", r.Method, r.URL.Path, duration)
    })
}

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}

func main() {
    store := NewUserStore()
    
    // 테스트 데이터 추가
    store.Create("홍길동", "hong@example.com")
    store.Create("김영희", "kim@example.com")
    store.Create("박철수", "park@example.com")
    
    userHandler := NewUserHandler(store)
    
    // 라우팅 설정
    mux := http.NewServeMux()
    mux.Handle("/api/users", userHandler)
    mux.Handle("/api/users/", userHandler)
    
    // Health check 엔드포인트
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{
            "status": "healthy",
            "time":   time.Now().Format(time.RFC3339),
        })
    })
    
    // 미들웨어 적용
    handler := corsMiddleware(loggingMiddleware(mux))
    
    fmt.Println("서버가 :8080 포트에서 시작됩니다...")
    log.Fatal(http.ListenAndServe(":8080", handler))
}

이제 서버를 실행하고 테스트해보세요:

# 서버 실행
$ go run main.go

# 다른 터미널에서 테스트
$ curl http://localhost:8080/health
$ curl http://localhost:8080/api/users
$ curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"새사용자","email":"new@example.com"}'

고급 Go 패턴들

1. 에러 처리 패턴

package main

import (
    "errors"
    "fmt"
)

// 커스텀 에러 타입
type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Message)
}

// 에러 래핑
func validateUser(name, email string) error {
    if name == "" {
        return ValidationError{Field: "name", Message: "cannot be empty"}
    }
    
    if !strings.Contains(email, "@") {
        return ValidationError{Field: "email", Message: "invalid format"}
    }
    
    return nil
}

func createUser(name, email string) (*User, error) {
    if err := validateUser(name, email); err != nil {
        return nil, fmt.Errorf("user creation failed: %w", err)
    }
    
    // 사용자 생성 로직...
    return &User{Name: name, Email: email}, nil
}

func main() {
    _, err := createUser("", "invalid-email")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        
        // 특정 에러 타입 확인
        var validationErr ValidationError
        if errors.As(err, &validationErr) {
            fmt.Printf("Validation error in field: %s\n", validationErr.Field)
        }
    }
}

2. 컨텍스트 패턴

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func longRunningTask(ctx context.Context) error {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("작업 완료!")
        return nil
    case <-ctx.Done():
        fmt.Println("작업이 취소되었습니다:", ctx.Err())
        return ctx.Err()
    }
}

func handlerWithTimeout(w http.ResponseWriter, r *http.Request) {
    // 3초 타임아웃 컨텍스트
    ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
    defer cancel()
    
    if err := longRunningTask(ctx); err != nil {
        http.Error(w, "Request timeout", http.StatusRequestTimeout)
        return
    }
    
    w.Write([]byte("작업 완료!"))
}

func main() {
    http.HandleFunc("/task", handlerWithTimeout)
    fmt.Println("서버 시작: http://localhost:8080/task")
    http.ListenAndServe(":8080", nil)
}

3. 제네릭 (Go 1.18+)

package main

import "fmt"

// 제네릭 스택
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    
    index := len(s.items) - 1
    item := s.items[index]
    s.items = s.items[:index]
    return item, true
}

func (s *Stack[T]) IsEmpty() bool {
    return len(s.items) == 0
}

// 제네릭 함수
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func main() {
    // 정수 스택
    intStack := &Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)
    
    for !intStack.IsEmpty() {
        if value, ok := intStack.Pop(); ok {
            fmt.Println("Pop:", value)
        }
    }
    
    // 문자열 스택
    stringStack := &Stack[string]{}
    stringStack.Push("Hello")
    stringStack.Push("World")
    
    // 제네릭 함수 사용
    numbers := []int{1, 2, 3, 4, 5}
    doubled := Map(numbers, func(n int) int { return n * 2 })
    strings := Map(numbers, func(n int) string { return fmt.Sprintf("Number: %d", n) })
    
    fmt.Println("Doubled:", doubled)
    fmt.Println("Strings:", strings)
}

성능 최적화 팁들

1. 메모리 최적화

// 좋지 않은 예
func badSliceUsage() {
    data := make([]int, 0)  // 초기 용량 없음
    for i := 0; i < 10000; i++ {
        data = append(data, i)  // 계속 재할당
    }
}

// 좋은 예
func goodSliceUsage() {
    data := make([]int, 0, 10000)  // 용량 미리 할당
    for i := 0; i < 10000; i++ {
        data = append(data, i)
    }
}

// 문자열 빌더 사용
func buildString(parts []string) string {
    var builder strings.Builder
    builder.Grow(len(parts) * 10)  // 예상 크기만큼 미리 할당
    
    for _, part := range parts {
        builder.WriteString(part)
    }
    
    return builder.String()
}

2. 고루틴 풀 패턴

package main

import (
    "fmt"
    "sync"
    "time"
)

type WorkerPool struct {
    workerCount int
    jobQueue    chan Job
    wg          sync.WaitGroup
}

type Job struct {
    ID   int
    Data string
}

func NewWorkerPool(workerCount, queueSize int) *WorkerPool {
    return &WorkerPool{
        workerCount: workerCount,
        jobQueue:    make(chan Job, queueSize),
    }
}

func (wp *WorkerPool) Start() {
    for i := 0; i < wp.workerCount; i++ {
        wp.wg.Add(1)
        go wp.worker(i)
    }
}

func (wp *WorkerPool) worker(id int) {
    defer wp.wg.Done()
    
    for job := range wp.jobQueue {
        fmt.Printf("Worker %d processing job %d: %s\n", id, job.ID, job.Data)
        time.Sleep(time.Millisecond * 100) // 작업 시뮬레이션
    }
}

func (wp *WorkerPool) Submit(job Job) {
    wp.jobQueue <- job
}

func (wp *WorkerPool) Stop() {
    close(wp.jobQueue)
    wp.wg.Wait()
}

func main() {
    pool := NewWorkerPool(3, 10)
    pool.Start()
    
    // 작업 제출
    for i := 0; i < 20; i++ {
        pool.Submit(Job{
            ID:   i,
            Data: fmt.Sprintf("Task %d", i),
        })
    }
    
    pool.Stop()
    fmt.Println("모든 작업 완료!")
}

실무에서 자주 쓰이는 도구들

1. 환경 설정 관리

package main

import (
    "os"
    "strconv"
)

type Config struct {
    Port        int    `json:"port"`
    DatabaseURL string `json:"database_url"`
    Debug       bool   `json:"debug"`
}

func LoadConfig() *Config {
    return &Config{
        Port:        getEnvAsInt("PORT", 8080),
        DatabaseURL: getEnvAsString("DATABASE_URL", "localhost:5432"),
        Debug:       getEnvAsBool("DEBUG", false),
    }
}

func getEnvAsString(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

func getEnvAsInt(key string, defaultValue int) int {
    if value := os.Getenv(key); value != "" {
        if intValue, err := strconv.Atoi(value); err == nil {
            return intValue
        }
    }
    return defaultValue
}

func getEnvAsBool(key string, defaultValue bool) bool {
    if value := os.Getenv(key); value != "" {
        if boolValue, err := strconv.ParseBool(value); err == nil {
            return boolValue
        }
    }
    return defaultValue
}

2. 로깅

package main

import (
    "log"
    "log/slog"
    "os"
)

func setupLogger() *slog.Logger {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelDebug,
    }))
    
    return logger
}

func main() {
    logger := setupLogger()
    
    logger.Info("서버 시작", "port", 8080)
    logger.Error("데이터베이스 연결 실패", "error", "connection timeout")
    logger.Debug("요청 처리", "method", "GET", "path", "/api/users", "duration", "45ms")
}

테스트 작성하기

package main

import (
    "testing"
)

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"mixed", -1, 2, 1},
        {"zero", 0, 5, 5},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

// 벤치마크 테스트
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        add(1, 2)
    }
}

func add(a, b int) int {
    return a + b
}

배포와 운영

Dockerfile 예제

# 멀티 스테이지 빌드
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

# 실행 환경
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/

COPY --from=builder /app/main .

EXPOSE 8080
CMD ["./main"]

Makefile

.PHONY: build run test clean docker

build:
	go build -o bin/app main.go

run:
	go run main.go

test:
	go test -v ./...

bench:
	go test -bench=. ./...

clean:
	rm -rf bin/

docker:
	docker build -t myapp .

docker-run:
	docker run -p 8080:8080 myapp

결론: Go는 선택이 아닌 필수

왜 Go를 배워야 하는가?

  1. 산업 표준: 쿠버네티스, 도커, 프로메테우스 등 모든 클라우드 도구들이 Go
  2. 성능: C/C++ 급의 성능과 Python 급의 개발 속도
  3. 동시성: 고루틴으로 쉽게 처리하는 대규모 동시 처리
  4. 생산성: 컴파일 속도, 배포 편의성, 코드 가독성

지금 바로 시작하세요:

# Go 설치
go version

# 첫 프로젝트
mkdir my-go-app
cd my-go-app
go mod init my-go-app
echo 'package main\nimport "fmt"\nfunc main() { fmt.Println("Hello, Go!") }' > main.go
go run main.go

 

7일 후, 여러분은 "왜 진작 Go를 배우지 않았을까?"라고 후회하게 될 겁니다.

 

구글, 넷플릭스, 우버가 선택한 언어, 이제 여러분 차례입니다!

728x90
반응형