๐ Go ์ธ์ด Day 1: ์์๋ถํฐ ์ค๋ฌด ์ฝ๋๊น์ง
๐ Todayโs Goal
โ์ค๋ ๋๋๊ณ ๋๋ฉด Go๋ก ๊ฐ๋จํ REST API ์๋ฒ๋ฅผ ๋ง๋ค ์ ์๋ค!โ
๐ฏ ํ์ต ๋ชฉํ (8์๊ฐ)
- Go ํ๊ฒฝ ์ค์ & ๊ธฐ๋ณธ ๋ฌธ๋ฒ (2์๊ฐ)
- Go๋ง์ ํน๋ณํ ๊ธฐ๋ฅ ์ดํด (2์๊ฐ)
- ์ค์ ํ๋ก์ ํธ: 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 Explorer1.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
./myapp1.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 ์ค๊ณ
๐ ์์
-
Todo API์ ๋ค์ ๊ธฐ๋ฅ ์ถ๊ฐ:
- ๊ฒ์ ๊ธฐ๋ฅ (query parameter)
- ์ ๋ ฌ ๊ธฐ๋ฅ (created_at, completed)
- ํ์ด์ง๋ค์ด์
-
์๋ฌ ์ฒ๋ฆฌ ๊ฐ์ :
- Custom error types
- Proper HTTP status codes
๐ ๋ด์ผ ์๊ณ (Day 2)
- Database ์ฐ๋ (PostgreSQL)
- Context ํ์ฉ
- Testing ์์ฑ
- Docker ๋ฐฐํฌ
๐ก ์ถ๊ฐ ํ์ต ์๋ฃ
๐ ์ถํํฉ๋๋ค! Day 1์ ์๋ฃํ์ต๋๋ค. ์ด์ ๋น์ ์ Go๋ก REST API๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค!