๐Ÿš€ Go ์–ธ์–ด Day 1: ์‹œ์ž‘๋ถ€ํ„ฐ ์‹ค๋ฌด ์ฝ”๋“œ๊นŒ์ง€

๐Ÿ“‹ Todayโ€™s Goal

โ€œ์˜ค๋Š˜ ๋๋‚˜๊ณ  ๋‚˜๋ฉด Go๋กœ ๊ฐ„๋‹จํ•œ REST API ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค!โ€

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ (8์‹œ๊ฐ„)

  1. Go ํ™˜๊ฒฝ ์„ค์ • & ๊ธฐ๋ณธ ๋ฌธ๋ฒ• (2์‹œ๊ฐ„)
  2. Go๋งŒ์˜ ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ ์ดํ•ด (2์‹œ๊ฐ„)
  3. ์‹ค์ „ ํ”„๋กœ์ ํŠธ: TODO API ๋งŒ๋“ค๊ธฐ (4์‹œ๊ฐ„)

๐Ÿ“š Part 1: Go ํ™˜๊ฒฝ ์„ค์ • & ๊ธฐ๋ณธ (1-2์‹œ๊ฐ„)

1.1 ์„ค์น˜ & ํ™˜๊ฒฝ์„ค์ •

# Go ์„ค์น˜ ํ™•์ธ
go version
 
# ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
mkdir go-day1 && cd go-day1
go mod init github.com/yourusername/go-day1
 
# VS Code ํ™•์žฅ ์„ค์น˜
# - Go (๊ณต์‹)
# - Go Test Explorer

1.2 Hello, Go! (์ฒซ 10๋ถ„)

// main.go
package main
 
import "fmt"
 
func main() {
    fmt.Println("Hello, Go!")
}
# ์‹คํ–‰
go run main.go
 
# ๋นŒ๋“œ
go build -o myapp main.go
./myapp

1.3 Go ๊ธฐ๋ณธ ๋ฌธ๋ฒ• ์†์„ฑ (30๋ถ„)

package main
 
import (
    "fmt"
    "errors"
)
 
// 1. ๋ณ€์ˆ˜ ์„ ์–ธ
func variables() {
    // var ๋ช…์‹œ์  ์„ ์–ธ
    var name string = "Go"
    var age int = 13
    
    // ํƒ€์ž… ์ถ”๋ก 
    var language = "Golang"
    
    // Short declaration (ํ•จ์ˆ˜ ๋‚ด๋ถ€๋งŒ)
    nickname := "Gopher"
    
    // ๋‹ค์ค‘ ์„ ์–ธ
    var (
        x int    = 1
        y string = "hello"
    )
    
    fmt.Printf("Name: %s, Age: %d\n", name, age)
}
 
// 2. ํ•จ์ˆ˜ - ๋‹ค์ค‘ ๋ฐ˜ํ™˜๊ฐ’!
func getUserInfo(id int) (string, int, error) {
    if id <= 0 {
        return "", 0, errors.New("invalid id")
    }
    return "John", 25, nil
}
 
// 3. ๊ตฌ์กฐ์ฒด
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}
 
// 4. ๋ฉ”์„œ๋“œ
func (u User) Greeting() string {
    return fmt.Sprintf("Hi, I'm %s", u.Name)
}
 
// 5. ์ธํ„ฐํŽ˜์ด์Šค
type Greeter interface {
    Greeting() string
}
 
func main() {
    // ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํŒจํ„ด (Go์˜ ํ•ต์‹ฌ!)
    name, age, err := getUserInfo(1)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("User: %s, %d\n", name, age)
    
    // ๊ตฌ์กฐ์ฒด ์‚ฌ์šฉ
    user := User{
        ID:   1,
        Name: "Alice",
        Age:  30,
    }
    fmt.Println(user.Greeting())
}

1.4 Go ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ (30๋ถ„)

package main
 
import (
    "fmt"
    "time"
)
 
// 1. Goroutines (๋™์‹œ์„ฑ)
func printNumbers(prefix string) {
    for i := 1; i <= 5; i++ {
        fmt.Printf("%s: %d\n", prefix, i)
        time.Sleep(100 * time.Millisecond)
    }
}
 
// 2. Channels
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(time.Second)
        results <- job * 2
    }
}
 
// 3. Defer (์ •๋ฆฌ ์ž‘์—…)
func fileOperation() error {
    fmt.Println("Opening file...")
    defer fmt.Println("Closing file...") // ํ•จ์ˆ˜ ๋๋‚  ๋•Œ ์‹คํ–‰
    
    fmt.Println("Processing file...")
    return nil
}
 
func main() {
    // Goroutines ์‹คํ–‰
    go printNumbers("A")
    go printNumbers("B")
    time.Sleep(1 * time.Second)
    
    // Channel ์‚ฌ์šฉ
    jobs := make(chan int, 5)
    results := make(chan int, 5)
    
    // Worker ์‹œ์ž‘
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // Job ์ „์†ก
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    
    // ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    for r := 1; r <= 5; r++ {
        <-results
    }
}

๐Ÿ”จ Part 2: ์‹ค์ „ ํ”„๋กœ์ ํŠธ - TODO REST API (4์‹œ๊ฐ„)

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

go-day1/
โ”œโ”€โ”€ main.go
โ”œโ”€โ”€ handlers/
โ”‚   โ””โ”€โ”€ todo.go
โ”œโ”€โ”€ models/
โ”‚   โ””โ”€โ”€ todo.go
โ””โ”€โ”€ go.mod

Step 1: ๋ชจ๋ธ ์ •์˜

// models/todo.go
package models
 
import "time"
 
type Todo struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Completed bool      `json:"completed"`
    CreatedAt time.Time `json:"created_at"`
}
 
type CreateTodoRequest struct {
    Title string `json:"title"`
}
 
type UpdateTodoRequest struct {
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

Step 2: ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„

// handlers/todo.go
package handlers
 
import (
    "encoding/json"
    "net/http"
    "strconv"
    "sync"
    "time"
    
    "github.com/gorilla/mux"
    "github.com/yourusername/go-day1/models"
)
 
type TodoHandler struct {
    todos  map[int]*models.Todo
    nextID int
    mu     sync.RWMutex
}
 
func NewTodoHandler() *TodoHandler {
    return &TodoHandler{
        todos:  make(map[int]*models.Todo),
        nextID: 1,
    }
}
 
// GET /todos
func (h *TodoHandler) GetTodos(w http.ResponseWriter, r *http.Request) {
    h.mu.RLock()
    defer h.mu.RUnlock()
    
    todos := make([]*models.Todo, 0, len(h.todos))
    for _, todo := range h.todos {
        todos = append(todos, todo)
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todos)
}
 
// GET /todos/{id}
func (h *TodoHandler) GetTodo(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    h.mu.RLock()
    todo, exists := h.todos[id]
    h.mu.RUnlock()
    
    if !exists {
        http.Error(w, "Todo not found", http.StatusNotFound)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todo)
}
 
// POST /todos
func (h *TodoHandler) CreateTodo(w http.ResponseWriter, r *http.Request) {
    var req models.CreateTodoRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }
    
    h.mu.Lock()
    todo := &models.Todo{
        ID:        h.nextID,
        Title:     req.Title,
        Completed: false,
        CreatedAt: time.Now(),
    }
    h.todos[h.nextID] = todo
    h.nextID++
    h.mu.Unlock()
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(todo)
}
 
// PUT /todos/{id}
func (h *TodoHandler) UpdateTodo(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    var req models.UpdateTodoRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }
    
    h.mu.Lock()
    todo, exists := h.todos[id]
    if !exists {
        h.mu.Unlock()
        http.Error(w, "Todo not found", http.StatusNotFound)
        return
    }
    
    todo.Title = req.Title
    todo.Completed = req.Completed
    h.mu.Unlock()
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todo)
}
 
// DELETE /todos/{id}
func (h *TodoHandler) DeleteTodo(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    h.mu.Lock()
    _, exists := h.todos[id]
    if !exists {
        h.mu.Unlock()
        http.Error(w, "Todo not found", http.StatusNotFound)
        return
    }
    
    delete(h.todos, id)
    h.mu.Unlock()
    
    w.WriteHeader(http.StatusNoContent)
}

Step 3: ๋ฉ”์ธ ์„œ๋ฒ„

// main.go
package main
 
import (
    "log"
    "net/http"
    "os"
    "time"
    
    "github.com/gorilla/mux"
    "github.com/yourusername/go-day1/handlers"
)
 
// Middleware
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.RequestURI, time.Since(start))
        next.ServeHTTP(w, r)
    })
}
 
func main() {
    todoHandler := handlers.NewTodoHandler()
    
    r := mux.NewRouter()
    r.Use(loggingMiddleware)
    
    // Routes
    r.HandleFunc("/todos", todoHandler.GetTodos).Methods("GET")
    r.HandleFunc("/todos", todoHandler.CreateTodo).Methods("POST")
    r.HandleFunc("/todos/{id}", todoHandler.GetTodo).Methods("GET")
    r.HandleFunc("/todos/{id}", todoHandler.UpdateTodo).Methods("PUT")
    r.HandleFunc("/todos/{id}", todoHandler.DeleteTodo).Methods("DELETE")
    
    // Health check
    r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    }).Methods("GET")
    
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    
    log.Printf("Server starting on port %s", port)
    if err := http.ListenAndServe(":"+port, r); err != nil {
        log.Fatal(err)
    }
}

Step 4: ์˜์กด์„ฑ ์„ค์น˜ & ์‹คํ–‰

# Gorilla Mux ์„ค์น˜
go get -u github.com/gorilla/mux
 
# ์‹คํ–‰
go run main.go
 
# ๋‹ค๋ฅธ ํ„ฐ๋ฏธ๋„์—์„œ ํ…Œ์ŠคํŠธ
# Create
curl -X POST http://localhost:8080/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn Go"}'
 
# Get all
curl http://localhost:8080/todos
 
# Update
curl -X PUT http://localhost:8080/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn Go","completed":true}'
 
# Delete
curl -X DELETE http://localhost:8080/todos/1

๐ŸŽฏ Day 1 ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ์˜ค๋Š˜ ๋ฐฐ์šด ๊ฒƒ

  • Go ๊ธฐ๋ณธ ๋ฌธ๋ฒ• (๋ณ€์ˆ˜, ํ•จ์ˆ˜, ๊ตฌ์กฐ์ฒด)
  • Error handling ํŒจํ„ด
  • Goroutines & Channels ๊ธฐ์ดˆ
  • HTTP ์„œ๋ฒ„ ๊ตฌํ˜„
  • REST API ์„ค๊ณ„

๐Ÿ“ ์ˆ™์ œ

  1. Todo API์— ๋‹ค์Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€:

    • ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ (query parameter)
    • ์ •๋ ฌ ๊ธฐ๋Šฅ (created_at, completed)
    • ํŽ˜์ด์ง€๋„ค์ด์…˜
  2. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐœ์„ :

    • Custom error types
    • Proper HTTP status codes

๐Ÿš€ ๋‚ด์ผ ์˜ˆ๊ณ  (Day 2)

  • Database ์—ฐ๋™ (PostgreSQL)
  • Context ํ™œ์šฉ
  • Testing ์ž‘์„ฑ
  • Docker ๋ฐฐํฌ

๐Ÿ’ก ์ถ”๊ฐ€ ํ•™์Šต ์ž๋ฃŒ

๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! Day 1์„ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋‹น์‹ ์€ Go๋กœ REST API๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!