総合プロジェクト(パート2)

レッスン30:総合プロジェクト(パート2)

現実世界のアナロジー

前回のレッスンでは「タスク管理システムTaskFlow」の要件分析、ディレクトリ構造設計、データモデル、コアビジネスロジックを完成させました。家を建てるのに、基礎が敷かれ骨組みが立ったようなものです。このレッスンでは仕上げと引き渡しを完成させます:

このレッスンの後、デプロイ可能、テスト可能、ドキュメント化された完全なGoプロジェクトを手にできます。


プロジェクトの振り返り

前回のレッスンではTaskFlowのコア骨格を構築しました。このレッスンではそれをさらに洗練させます。レッスン29:総合プロジェクト(パート1)をまだお読みでない場合は、まず前半を完了することをお勧めします。

最終プロジェクト構造:

TEXT
taskflow/
├── cmd/
│   └── server/
│       └── main.go            # Program entry point
├── internal/
│   ├── model/
│   │   ├── task.go            # Data model
│   │   └── task_test.go       # Model tests
│   ├── store/
│   │   ├── store.go           # Storage interface
│   │   ├── memory.go          # In-memory implementation (previous lesson)
│   │   └── sqlite.go          # SQLite implementation (this lesson)
│   ├── service/
│   │   ├── task.go            # Business logic
│   │   └── task_test.go       # Unit tests
│   ├── handler/
│   │   ├── task.go            # HTTP handler
│   │   └── task_test.go       # Handler tests
│   └── middleware/
│       ├── auth.go            # Authentication middleware
│       ├── logging.go         # Logging middleware
│       └── cors.go            # CORS middleware
├── docs/
│   └── api.md                 # API documentation
├── Dockerfile                 # Container packaging
├── docker-compose.yml         # Orchestration config
├── go.mod
├── go.sum
└── README.md

1. REST APIレイヤー完成

1.1 ルート設計

RESTful規約に基づいて設計されたAPIルート:

メソッド パス 説明
GET /api/tasks タスクリストを取得
GET /api/tasks/{id} 単一タスクを取得
POST /api/tasks タスクを作成
PUT /api/tasks/{id} タスクを更新
DELETE /api/tasks/{id} タスクを削除
GET /health ヘルスチェック

1.2 完全なハンドラ実装

GO
// internal/handler/task.go
package handler

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"

	"taskflow/internal/model"
	"taskflow/internal/service"
)

// TaskHandler task HTTP handler
type TaskHandler struct {
	svc *service.TaskService
}

// NewTaskHandler creates a handler instance
func NewTaskHandler(svc *service.TaskService) *TaskHandler {
	return &TaskHandler{svc: svc}
}

// ErrorResponse unified error response format
type ErrorResponse struct {
	Error   string `json:"error"`
	Code    int    `json:"code"`
	Message string `json:"message,omitempty"`
}

// SuccessResponse unified success response format
type SuccessResponse struct {
	Data    interface{} `json:"data"`
	Message string      `json:"message,omitempty"`
}

// writeJSON writes a JSON response
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(data)
}

// writeError writes an error response
func writeError(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, ErrorResponse{
		Error: msg,
		Code:  status,
	})
}

// extractID extracts the ID parameter from the URL path
func extractID(r *http.Request) (int, error) {
	// Path format: /api/tasks/{id}
	parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(parts) < 3 {
		return 0, errors.New("missing ID parameter")
	}
	return strconv.Atoi(parts[2])
}

// ListTasks handles GET /api/tasks
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
	// Parse query parameters
	query := r.URL.Query()
	filter := service.TaskFilter{
		Status: model.TaskStatus(query.Get("status")),
	}

	// Parse pagination parameters
	page, _ := strconv.Atoi(query.Get("page"))
	pageSize, _ := strconv.Atoi(query.Get("page_size"))
	if page < 1 {
		page = 1
	}
	if pageSize < 1 || pageSize > 100 {
		pageSize = 20
	}

	tasks, total, err := h.svc.ListTasks(r.Context(), filter, page, pageSize)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "Query failed: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"data":      tasks,
		"total":     total,
		"page":      page,
		"page_size": pageSize,
	})
}

// GetTask handles GET /api/tasks/{id}
func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "Invalid task ID")
		return
	}

	task, err := h.svc.GetTask(r.Context(), id)
	if err != nil {
		if errors.Is(err, service.ErrTaskNotFound) {
			writeError(w, http.StatusNotFound, "Task not found")
			return
		}
		writeError(w, http.StatusInternalServerError, "Query failed")
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{Data: task})
}

// CreateTask handles POST /api/tasks
func (h *TaskHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
	var req model.CreateTaskRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "Invalid request format")
		return
	}

	task, err := h.svc.CreateTask(r.Context(), &req)
	if err != nil {
		if errors.Is(err, service.ErrValidation) {
			writeError(w, http.StatusBadRequest, err.Error())
			return
		}
		writeError(w, http.StatusInternalServerError, "Creation failed")
		return
	}

	writeJSON(w, http.StatusCreated, SuccessResponse{
		Data:    task,
		Message: "Task created successfully",
	})
}

// UpdateTask handles PUT /api/tasks/{id}
func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "Invalid task ID")
		return
	}

	var req model.UpdateTaskRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "Invalid request format")
		return
	}

	task, err := h.svc.UpdateTask(r.Context(), id, &req)
	if err != nil {
		switch {
		case errors.Is(err, service.ErrTaskNotFound):
			writeError(w, http.StatusNotFound, "Task not found")
		case errors.Is(err, service.ErrValidation):
			writeError(w, http.StatusBadRequest, err.Error())
		default:
			writeError(w, http.StatusInternalServerError, "Update failed")
		}
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{
		Data:    task,
		Message: "Task updated successfully",
	})
}

// DeleteTask handles DELETE /api/tasks/{id}
func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "Invalid task ID")
		return
	}

	if err := h.svc.DeleteTask(r.Context(), id); err != nil {
		if errors.Is(err, service.ErrTaskNotFound) {
			writeError(w, http.StatusNotFound, "Task not found")
			return
		}
		writeError(w, http.StatusInternalServerError, "Deletion failed")
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{Message: "Task deleted successfully"})
}

// HealthCheck health check endpoint
func (h *TaskHandler) HealthCheck(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, map[string]string{
		"status": "ok",
		"service": "taskflow",
	})
}

1.3 ルート登録とサーバー起動

GO
// cmd/server/main.go
package main

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"taskflow/internal/handler"
	"taskflow/internal/middleware"
	"taskflow/internal/service"
	"taskflow/internal/store"
)

func main() {
	// Initialize logging
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)

	// Initialize storage layer (default to SQLite)
	dbPath := getEnv("DB_PATH", "taskflow.db")
	st, err := store.NewSQLiteStore(dbPath)
	if err != nil {
		slog.Error("Database initialization failed", "error", err)
		os.Exit(1)
	}
	defer st.Close()

	// Initialize business layer
	svc := service.NewTaskService(st)

	// Initialize handlers
	h := handler.NewTaskHandler(svc)

	// Register routes
	mux := http.NewServeMux()

	// Health check
	mux.HandleFunc("GET /health", h.HealthCheck)

	// API routes
	mux.HandleFunc("GET /api/tasks", h.ListTasks)
	mux.HandleFunc("POST /api/tasks", h.CreateTask)
	mux.HandleFunc("GET /api/tasks/{id}", h.GetTask)
	mux.HandleFunc("PUT /api/tasks/{id}", h.UpdateTask)
	mux.HandleFunc("DELETE /api/tasks/{id}", h.DeleteTask)

	// Assemble middleware chain: CORS → Logging → Auth → Business handler
	apiKey := getEnv("API_KEY", "dev-secret-key")
	handlerChain := middleware.Chain(
		mux,
		middleware.CORS(),
		middleware.Logging(),
		middleware.Auth(apiKey),
	)

	// Create HTTP server
	addr := ":" + getEnv("PORT", "8080")
	srv := &http.Server{
		Addr:         addr,
		Handler:      handlerChain,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// Start server (non-blocking)
	go func() {
		slog.Info("TaskFlow service started", "addr", addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			slog.Error("Server exited abnormally", "error", err)
			os.Exit(1)
		}
	}()

	// Graceful shutdown: listen for system signals
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	slog.Info("Received shutdown signal, gracefully shutting down...")

	// Give in-progress requests 5 seconds to complete
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		slog.Error("Server shutdown error", "error", err)
	}

	slog.Info("TaskFlow service stopped")
}

// getEnv gets an environment variable with default value support
func getEnv(key, defaultVal string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return defaultVal
}
💡 Go 1.22+ Route Enhancement: Starting from Go 1.22, net/http supports method matching ("GET /path") and path parameters ({id}), allowing you to build RESTful APIs without third-party routing libraries.


2. ミドルウェア統合

2.1 ミドルウェアチェーンパターン

Middleware are "interceptors" in the HTTP request processing pipeline that can execute common logic before and after requests reach the handler:

TEXT
Request → [CORS] → [Logging] → [Auth] → [Handler] → Response
           ↓         ↓          ↓
       Cross-origin  Record    Verify
       control       duration  identity

2.2 ミドルウェアインフラストラクチャ

GO
// internal/middleware/middleware.go
package middleware

import "net/http"

// Middleware type definition
type Middleware func(http.Handler) http.Handler

// Chain combines multiple middleware into a chain, first passed = first executed
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
	// Wrap from back to front, ensuring execution order is left to right
	for i := len(middlewares) - 1; i >= 0; i-- {
		h = middlewares[i](h)
	}
	return h
}

2.3 CORSミドルウェア

GO
// internal/middleware/cors.go
package middleware

import "net/http"

// CORS handles Cross-Origin Resource Sharing
func CORS() Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Set CORS response headers
			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, X-Request-ID")
			w.Header().Set("Access-Control-Max-Age", "86400") // Preflight cache 24 hours

			// Preflight request returns directly
			if r.Method == http.MethodOptions {
				w.WriteHeader(http.StatusNoContent)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

2.4 ログ記録ミドルウェア

GO
// internal/middleware/logging.go
package middleware

import (
	"log/slog"
	"net/http"
	"time"
)

// responseWriter wraps ResponseWriter to capture status code
type responseWriter struct {
	http.ResponseWriter
	statusCode int
	written    int64
}

func newResponseWriter(w http.ResponseWriter) *responseWriter {
	return &responseWriter{
		ResponseWriter: w,
		statusCode:     http.StatusOK,
	}
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.statusCode = code
	rw.ResponseWriter.WriteHeader(code)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	n, err := rw.ResponseWriter.Write(b)
	rw.written += int64(n)
	return n, err
}

// Logging records processing logs for each request
func Logging() Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()

			// Wrap ResponseWriter to capture status code
			rw := newResponseWriter(w)

			// Call next handler
			next.ServeHTTP(rw, r)

			// Record request log
			duration := time.Since(start)
			slog.Info("HTTP request",
				"method", r.Method,
				"path", r.URL.Path,
				"status", rw.statusCode,
				"duration_ms", duration.Milliseconds(),
				"remote_addr", r.RemoteAddr,
				"user_agent", r.UserAgent(),
			)
		})
	}
}

2.5 認証ミドルウェア

GO
// internal/middleware/auth.go
package middleware

import (
	"net/http"
	"strings"
)

// Auth validates API key
func Auth(apiKey string) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Health check endpoint doesn't need authentication
			if r.URL.Path == "/health" {
				next.ServeHTTP(w, r)
				return
			}

			// Extract token from request header
			authHeader := r.Header.Get("Authorization")
			if authHeader == "" {
				w.Header().Set("WWW-Authenticate", `Bearer realm="taskflow"`)
				http.Error(w, `{"error":"Missing authentication","code":401}`, http.StatusUnauthorized)
				return
			}

			// Validate Bearer token format
			parts := strings.SplitN(authHeader, " ", 2)
			if len(parts) != 2 || parts[0] != "Bearer" {
				http.Error(w, `{"error":"Invalid auth format, should be Bearer <token>","code":401}`, http.StatusUnauthorized)
				return
			}

			// Validate token value
			if parts[1] != apiKey {
				http.Error(w, `{"error":"Authentication failed: invalid API key","code":403}`, http.StatusForbidden)
				return
			}

			// Authentication passed, continue processing
			next.ServeHTTP(w, r)
		})
	}
}

3. データベース統合(SQLite)

3.1 なぜSQLite?

特徴 SQLite MySQL/PostgreSQL
インストール ゼロ設定、単一ファイル 別のサービスが必要
用途 中小プロジェクト、組み込み 大規模本番環境
並行性 読み取り並行、書き込み直列 高並行性
移行 自分で実装が必要 豊富なツール
💡 学習プロジェクトや中小アプリケーションでは、SQLiteが最適な出発点です。トラフィックが増加したら、シームレスにPostgreSQLに切り替えられます。

3.2 SQLiteストレージ実装

GO
// internal/store/sqlite.go
package store

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "github.com/mattn/go-sqlite3" // SQLite driver, anonymous import triggers initialization

	"taskflow/internal/model"
)

// SQLiteStore SQLite storage implementation
type SQLiteStore struct {
	db *sql.DB
}

// NewSQLiteStore creates and initializes SQLite storage
func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
	db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
	if err != nil {
		return nil, fmt.Errorf("failed to open database: %w", err)
	}

	// Verify connection
	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("database connection failed: %w", err)
	}

	// Configure connection pool
	db.SetMaxOpenConns(1) // SQLite single write, limit concurrent writes
	db.SetMaxIdleConns(1)
	db.SetConnMaxLifetime(0) // Connections don't expire

	// Execute table migration
	if err := migrate(db); err != nil {
		return nil, fmt.Errorf("database migration failed: %w", err)
	}

	return &SQLiteStore{db: db}, nil
}

// migrate executes database migration
func migrate(db *sql.DB) error {
	query := `
	CREATE TABLE IF NOT EXISTS tasks (
		id          INTEGER PRIMARY KEY AUTOINCREMENT,
		title       TEXT    NOT NULL,
		description TEXT    NOT NULL DEFAULT '',
		status      TEXT    NOT NULL DEFAULT 'pending',
		priority    TEXT    NOT NULL DEFAULT 'medium',
		due_date    DATETIME,
		created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
		updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
	);

	CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
	CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
	CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date);
	`
	_, err := db.Exec(query)
	return err
}

// Close closes the database connection
func (s *SQLiteStore) Close() error {
	return s.db.Close()
}

// Create creates a task
func (s *SQLiteStore) Create(ctx context.Context, task *model.Task) error {
	query := `
		INSERT INTO tasks (title, description, status, priority, due_date, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`
	now := time.Now()
	result, err := s.db.ExecContext(ctx, query,
		task.Title, task.Description, task.Status, task.Priority,
		task.DueDate, now, now,
	)
	if err != nil {
		return fmt.Errorf("failed to insert task: %w", err)
	}

	id, err := result.LastInsertId()
	if err != nil {
		return fmt.Errorf("failed to get insert ID: %w", err)
	}

	task.ID = int(id)
	task.CreatedAt = now
	task.UpdatedAt = now
	return nil
}

// GetByID queries a task by ID
func (s *SQLiteStore) GetByID(ctx context.Context, id int) (*model.Task, error) {
	query := `
		SELECT id, title, description, status, priority, due_date, created_at, updated_at
		FROM tasks WHERE id = ?
	`
	var task model.Task
	var dueDate sql.NullTime

	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&task.ID, &task.Title, &task.Description,
		&task.Status, &task.Priority, &dueDate,
		&task.CreatedAt, &task.UpdatedAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil // Not found returns nil
	}
	if err != nil {
		return nil, fmt.Errorf("failed to query task: %w", err)
	}

	if dueDate.Valid {
		task.DueDate = &dueDate.Time
	}
	return &task, nil
}

// List queries task list (supports filtering and pagination)
func (s *SQLiteStore) List(ctx context.Context, filter TaskFilter, page, pageSize int) ([]model.Task, int, error) {
	// Build query conditions
	where := "1=1"
	args := []interface{}{}

	if filter.Status != "" {
		where += " AND status = ?"
		args = append(args, filter.Status)
	}
	if filter.Priority != "" {
		where += " AND priority = ?"
		args = append(args, filter.Priority)
	}

	// Query total count
	countQuery := fmt.Sprintf("SELECT COUNT(*) FROM tasks WHERE %s", where)
	var total int
	if err := s.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil {
		return nil, 0, fmt.Errorf("failed to query total: %w", err)
	}

	// Paginated query
	offset := (page - 1) * pageSize
	query := fmt.Sprintf(`
		SELECT id, title, description, status, priority, due_date, created_at, updated_at
		FROM tasks WHERE %s
		ORDER BY created_at DESC
		LIMIT ? OFFSET ?
	`, where)
	args = append(args, pageSize, offset)

	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to query list: %w", err)
	}
	defer rows.Close()

	var tasks []model.Task
	for rows.Next() {
		var task model.Task
		var dueDate sql.NullTime
		if err := rows.Scan(
			&task.ID, &task.Title, &task.Description,
			&task.Status, &task.Priority, &dueDate,
			&task.CreatedAt, &task.UpdatedAt,
		); err != nil {
			return nil, 0, fmt.Errorf("failed to scan row: %w", err)
		}
		if dueDate.Valid {
			task.DueDate = &dueDate.Time
		}
		tasks = append(tasks, task)
	}

	return tasks, total, nil
}

// Update updates a task
func (s *SQLiteStore) Update(ctx context.Context, task *model.Task) error {
	query := `
		UPDATE tasks
		SET title = ?, description = ?, status = ?, priority = ?, due_date = ?, updated_at = ?
		WHERE id = ?
	`
	result, err := s.db.ExecContext(ctx, query,
		task.Title, task.Description, task.Status, task.Priority,
		task.DueDate, time.Now(), task.ID,
	)
	if err != nil {
		return fmt.Errorf("failed to update task: %w", err)
	}

	rows, _ := result.RowsAffected()
	if rows == 0 {
		return fmt.Errorf("task not found")
	}
	return nil
}

// Delete deletes a task
func (s *SQLiteStore) Delete(ctx context.Context, id int) error {
	query := "DELETE FROM tasks WHERE id = ?"
	result, err := s.db.ExecContext(ctx, query, id)
	if err != nil {
		return fmt.Errorf("failed to delete task: %w", err)
	}

	rows, _ := result.RowsAffected()
	if rows == 0 {
		return fmt.Errorf("task not found")
	}
	return nil
}

// TaskFilter task filter conditions
type TaskFilter struct {
	Status   model.TaskStatus
	Priority model.TaskPriority
}

3.3 統一ストレージインターフェース

GO
// internal/store/store.go
package store

import (
	"context"

	"taskflow/internal/model"
)

// TaskStore defines the task storage interface
// All storage implementations (in-memory, SQLite, PostgreSQL) must implement this interface
type TaskStore interface {
	Create(ctx context.Context, task *model.Task) error
	GetByID(ctx context.Context, id int) (*model.Task, error)
	List(ctx context.Context, filter TaskFilter, page, pageSize int) ([]model.Task, int, error)
	Update(ctx context.Context, task *model.Task) error
	Delete(ctx context.Context, id int) error
	Close() error
}
💡 依存性注入:ビジネスレイヤーはTaskStoreインターフェースのみに依存し、基盤のストレージがSQLiteかインメモリかを気にしません。テストにはモックを注入し、本番には実際の実装を使用します。


4. 統合テスト

4.1 ハンドラ統合テスト

統合テストは複数のコンポーネントが正しく連携するかを検証し、httptestを使用して実際のHTTPリクエストをシミュレートします:

GO
// internal/handler/task_test.go
package handler_test

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"taskflow/internal/handler"
	"taskflow/internal/middleware"
	"taskflow/internal/model"
	"taskflow/internal/service"
	"taskflow/internal/store"
)

// setupTestServer creates a test server
func setupTestServer(t *testing.T) *httptest.Server {
	t.Helper()

	// Use in-memory storage
	memStore := store.NewMemoryStore()
	svc := service.NewTaskService(memStore)
	h := handler.NewTaskHandler(svc)

	// Register routes
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", h.HealthCheck)
	mux.HandleFunc("GET /api/tasks", h.ListTasks)
	mux.HandleFunc("POST /api/tasks", h.CreateTask)
	mux.HandleFunc("GET /api/tasks/{id}", h.GetTask)
	mux.HandleFunc("PUT /api/tasks/{id}", h.UpdateTask)
	mux.HandleFunc("DELETE /api/tasks/{id}", h.DeleteTask)

	// Add middleware
	apiKey := "test-key"
	handlerChain := middleware.Chain(
		mux,
		middleware.CORS(),
		middleware.Logging(),
		middleware.Auth(apiKey),
	)

	return httptest.NewServer(handlerChain)
}

// TestIntegration_CRUD complete CRUD flow integration test
func TestIntegration_CRUD(t *testing.T) {
	srv := setupTestServer(t)
	defer srv.Close()

	client := srv.Client()
	authHeader := "Bearer test-key"

	// 1. Health check
	t.Run("Health check", func(t *testing.T) {
		resp, err := client.Get(srv.URL + "/health")
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Status code = %d, expected 200", resp.StatusCode)
		}
	})

	// 2. Create task
	var createdTask model.Task
	t.Run("Create task", func(t *testing.T) {
		body := `{"title":"Integration test task","description":"Testing CRUD flow","priority":"high"}`
		req, _ := http.NewRequest(http.MethodPost, srv.URL+"/api/tasks", bytes.NewBufferString(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusCreated {
			t.Errorf("Status code = %d, expected 201", resp.StatusCode)
		}

		var result struct {
			Data    model.Task `json:"data"`
			Message string     `json:"message"`
		}
		json.NewDecoder(resp.Body).Decode(&result)
		createdTask = result.Data

		if createdTask.Title != "Integration test task" {
			t.Errorf("Title = %q, expected %q", createdTask.Title, "Integration test task")
		}
	})

	// 3. Query single task
	t.Run("Query task", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks/1", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Status code = %d, expected 200", resp.StatusCode)
		}
	})

	// 4. Update task
	t.Run("Update task", func(t *testing.T) {
		body := `{"title":"Updated task","status":"completed"}`
		req, _ := http.NewRequest(http.MethodPut, srv.URL+"/api/tasks/1", bytes.NewBufferString(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Status code = %d, expected 200", resp.StatusCode)
		}
	})

	// 5. Query list
	t.Run("Query list", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks?page=1&page_size=10", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Status code = %d, expected 200", resp.StatusCode)
		}

		var result struct {
			Data  []model.Task `json:"data"`
			Total int          `json:"total"`
		}
		json.NewDecoder(resp.Body).Decode(&result)

		if result.Total != 1 {
			t.Errorf("Total = %d, expected 1", result.Total)
		}
	})

	// 6. Delete task
	t.Run("Delete task", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/tasks/1", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Status code = %d, expected 200", resp.StatusCode)
		}
	})

	// 7. Verify query after deletion returns 404
	t.Run("Query after deletion should return 404", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks/1", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Request failed: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("Status code = %d, expected 404", resp.StatusCode)
		}
	})
}

// TestIntegration_Auth authentication middleware integration test
func TestIntegration_Auth(t *testing.T) {
	srv := setupTestServer(t)
	defer srv.Close()

	client := srv.Client()

	tests := []struct {
		name       string
		authHeader string
		wantCode   int
	}{
		{"No auth header", "", http.StatusUnauthorized},
		{"Wrong format", "Basic abc123", http.StatusUnauthorized},
		{"Wrong key", "Bearer wrong-key", http.StatusForbidden},
		{"Correct auth", "Bearer test-key", http.StatusOK},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks", nil)
			if tt.authHeader != "" {
				req.Header.Set("Authorization", tt.authHeader)
			}

			resp, err := client.Do(req)
			if err != nil {
				t.Fatalf("Request failed: %v", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != tt.wantCode {
				t.Errorf("Status code = %d, expected %d", resp.StatusCode, tt.wantCode)
			}
		})
	}
}

// TestIntegration_CORS cross-origin test
func TestIntegration_CORS(t *testing.T) {
	srv := setupTestServer(t)
	defer srv.Close()

	client := srv.Client()

	// OPTIONS preflight request
	req, _ := http.NewRequest(http.MethodOptions, srv.URL+"/api/tasks", nil)
	req.Header.Set("Origin", "http://example.com")
	req.Header.Set("Access-Control-Request-Method", "POST")

	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("Request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("Status code = %d, expected 204", resp.StatusCode)
	}

	if resp.Header.Get("Access-Control-Allow-Origin") != "*" {
		t.Error("Missing CORS response header")
	}
}

4.2 統合テストの実行

BASH
# Run all tests
go test ./... -v

# Only run integration tests
go test -run TestIntegration -v

# View coverage
go test ./... -cover -coverprofile=coverage.out

# Generate HTML coverage report
go tool cover -html=coverage.out -o coverage.html

# Race detection
go test -race ./...

5. APIドキュメント(Swagger/OpenAPI概要)

5.1 OpenAPI仕様概要

OpenAPI (formerly Swagger) is the standard specification for describing RESTful APIs, defining interfaces in YAML/JSON format:

YAML
# docs/api.yaml (simplified version)
openapi: "3.0.3"
info:
  title: TaskFlow API
  description: Task management system RESTful API
  version: "1.0.0"
  contact:
    name: Development Team
    email: dev@taskflow.example.com

servers:
  - url: http://localhost:8080
    description: Local development environment

paths:
  /api/tasks:
    get:
      summary: Get task list
      operationId: listTasks
      tags: [Task Management]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, in_progress, completed]
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: page_size
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        "200":
          description: Successfully returned task list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Task"
                  total:
                    type: integer
                  page:
                    type: integer

    post:
      summary: Create new task
      operationId: createTask
      tags: [Task Management]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTaskRequest"
      responses:
        "201":
          description: Task created successfully
        "400":
          description: Invalid request parameters

  /api/tasks/{id}:
    get:
      summary: Get single task
      operationId: getTask
      tags: [Task Management]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Success
        "404":
          description: Task not found

    put:
      summary: Update task
      operationId: updateTask
      tags: [Task Management]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Updated successfully
        "404":
          description: Task not found

    delete:
      summary: Delete task
      operationId: deleteTask
      tags: [Task Management]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Deleted successfully
        "404":
          description: Task not found

components:
  schemas:
    Task:
      type: object
      properties:
        id:
          type: integer
          example: 1
        title:
          type: string
          example: "Learn Go"
        description:
          type: string
          example: "Complete Lesson 30 comprehensive project"
        status:
          type: string
          enum: [pending, in_progress, completed]
        priority:
          type: string
          enum: [low, medium, high, urgent]
        due_date:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateTaskRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
        priority:
          type: string
          enum: [low, medium, high, urgent]
          default: medium
        due_date:
          type: string
          format: date-time
          nullable: true

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

security:
  - BearerAuth: []

5.2 Swaggoによるドキュメント自動生成

For Go projects, it's recommended to use swaggo/swag to auto-generate Swagger documentation from code comments:

GO
// Add comments above handler functions

// ListTasks godoc
// @Summary      Get task list
// @Description  Supports filtering by status and pagination
// @Tags         Task Management
// @Accept       json
// @Produce      json
// @Param        status   query    string  false  "Task status"
// @Param        page     query    int     false  "Page number"     default(1)
// @Param        page_size query   int     false  "Items per page"  default(20)
// @Success      200  {object}  map[string]interface{}
// @Failure      500  {object}  ErrorResponse
// @Security     BearerAuth
// @Router       /api/tasks [get]
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
    // ...
}

Generation command:

BASH
# Install swag tool
go install github.com/swaggo/swag/cmd/swag@latest

# Generate documentation in project root directory
swag init -g cmd/server/main.go

# Generated files are in docs/ directory
# docs/swagger.json, docs/swagger.yaml, docs/docs.go

6. README作成ガイドライン

An excellent README should include the following sections:

MARKDOWN
# TaskFlow - Task Management System

> A lightweight task management RESTful API service built with Go

## Features

- Complete task CRUD operations
- Filter by status and priority
- Pagination support
- API key authentication
- CORS cross-origin support
- Structured logging
- SQLite persistent storage
- One-click Docker deployment

## Quick Start

### Prerequisites

- Go 1.22+
- SQLite3 (development environment)

### Installation and Running

```bash
# Clone project
git clone https://github.com/yourname/taskflow.git
cd taskflow

# Install dependencies
go mod tidy

# Run service
go run cmd/server/main.go

# Service listens on http://localhost:8080 by default

Environment Variables

Variable Default Description
PORT 8080 Service port
DB_PATH taskflow.db SQLite database file path
API_KEY dev-secret-key API authentication key

API Usage Examples

BASH
# Create task
curl -X POST http://localhost:8080/api/tasks \
  -H "Authorization: Bearer dev-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn Go","priority":"high"}'

# Query list
curl http://localhost:8080/api/tasks \
  -H "Authorization: Bearer dev-secret-key"

# Health check (no auth needed)
curl http://localhost:8080/health

Project Structure

TEXT
taskflow/
├── cmd/server/         # Program entry point
├── internal/
│   ├── model/          # Data models
│   ├── store/          # Data storage layer
│   ├── service/        # Business logic layer
│   ├── handler/        # HTTP handler layer
│   └── middleware/     # Middleware
├── docs/               # API documentation
├── Dockerfile
└── README.md

Testing

BASH
# Run all tests
go test ./... -v

# View coverage
go test ./... -cover

# Race detection
go test -race ./...

Deployment

Docker Deployment

BASH
# Build image
docker build -t taskflow:latest .

# Run container
docker run -d \
  -p 8080:8080 \
  -e API_KEY=your-production-key \
  -e DB_PATH=/data/taskflow.db \
  -v taskflow-data:/data \
  --name taskflow \
  taskflow:latest

License

MIT License


> 💡 README Writing Principle: Get others running in 30 seconds first, then gradually expand details.

---

## 7. Dockerfileパッケージングとリリース

### 7.1 マルチステージビルド

```dockerfile
# Dockerfile

# ========== Stage 1: Build ==========
FROM golang:1.22-alpine AS builder

# Install SQLite compilation dependencies
RUN apk add --no-cache gcc musl-dev

WORKDIR /app

# Copy dependency files first, leveraging Docker cache layers
COPY go.mod go.sum ./
RUN go mod download

# Copy source and compile
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build \
    -ldflags="-s -w" \
    -o /app/taskflow \
    ./cmd/server/main.go

# ========== Stage 2: Run ==========
FROM alpine:3.19

# Install runtime dependencies
RUN apk add --no-cache ca-certificates tzdata

# Create non-root user
RUN adduser -D -g '' appuser

WORKDIR /app

# Copy binary from build stage
COPY --from=builder /app/taskflow .

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

# Startup command
ENTRYPOINT ["./taskflow"]

7.2 Docker Compose設定

YAML
# docker-compose.yml
version: "3.8"

services:
  taskflow:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - DB_PATH=/data/taskflow.db
      - API_KEY=${API_KEY:-change-me-in-production}
      - TZ=Asia/Shanghai
    volumes:
      - taskflow-data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3

volumes:
  taskflow-data:

7.3 ビルドと実行

BASH
# Build Docker image
docker build -t taskflow:latest .

# View image size
docker images taskflow

# Start with Docker Compose
export API_KEY=your-secret-key
docker compose up -d

# View logs
docker compose logs -f

# Stop service
docker compose down

# Stop and remove data volume
docker compose down -v

7.4 クロスコンパイル(Docker環境なしの場合)

BASH
# Compile Linux binary (on Windows/macOS)
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
    CC=x86_64-linux-musl-gcc \
    go build -ldflags="-s -w" -o taskflow-linux ./cmd/server/main.go

# Compile CGO-free version (doesn't support SQLite, needs pure Go database driver)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-s -w" -o taskflow-linux ./cmd/server/main.go

8. デプロイガイド

8.1 デプロイチェックリスト

TEXT
✅ Security Checks
   □ Production API_KEY changed to strong password
   □ CORS config restricts allowed origin domains
   □ Logs don't contain sensitive information
   □ Using HTTPS (configure reverse proxy)

✅ Performance Checks
   □ Database connection pool configured
   □ HTTP timeouts set (Read/Write/Idle)
   □ Request body size limited
   □ Paginated queries have maximum limits

✅ Reliability Checks
   □ Health check endpoint available
   □ Graceful shutdown logic implemented
   □ Log format is structured JSON
   □ Docker health check configured

8.2 Nginxリバースプロキシの使用

NGINX
# /etc/nginx/conf.d/taskflow.conf
server {
    listen 80;
    server_name api.taskflow.example.com;

    # Force HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.taskflow.example.com;

    ssl_certificate     /etc/ssl/certs/taskflow.crt;
    ssl_certificate_key /etc/ssl/private/taskflow.key;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeout settings
        proxy_connect_timeout 10s;
        proxy_read_timeout 30s;
        proxy_send_timeout 30s;
    }
}

❓ よくある質問

質問1:SQLiteがDockerで「database is locked」と表示される場合は?

回答: SQLiteは同時書き込みに制限があります。以下を確認してください:

GO
// 1. Set connection pool to 1 (single connection)
db.SetMaxOpenConns(1)

// 2. Enable WAL mode (allows read/write concurrency)
db.Exec("PRAGMA journal_mode=WAL")

// 3. Set busy wait timeout
// Add ?_busy_timeout=5000 to connection string

並行性が高い場合は、PostgreSQLへの切り替えを検討してください。

質問2:ミドルウェアの実行順序は重要ですか?

回答: はい。推奨順序:CORS → Logging → Auth → ビジネスハンドラ。理由:

GO
// In Chain, the first passed middleware executes first
handler := middleware.Chain(mux,
    middleware.CORS(),    // 1. Executes first
    middleware.Logging(), // 2. Second
    middleware.Auth(key), // 3. Executes last
)

質問3:本番環境でAPIキーをどう管理すべきですか?

回答: キーをハードコードしたり、コードリポジトリにコミットしたりしないでください。推奨方法:

BASH
# Method 1: Environment variables
export API_KEY=$(openssl rand -hex 32)

# Method 2: .env file (add to .gitignore)
echo "API_KEY=$(openssl rand -hex 32)" > .env

# Method 3: Docker Secrets (Swarm mode)
echo "my-secret-key" | docker secret create api_key -

質問4:プロジェクトにはSQLiteとPostgreSQLのどちらを使うべきですか?

回答: シナリオに基づいて選択してください:

シナリオ 推奨 理由
学習/プロトタイピング SQLite ゼロ設定、単一ファイル
社内ツール/低トラフィック SQLite 十分でメンテナンスが容易
複数サービスでデータ共有 PostgreSQL ネットワークアクセス、集中管理
高並行書き込み PostgreSQL SQLiteの書き込みは直列
JSONクエリが必要 PostgreSQL 強力なJSONBサポート

📖 コースまとめ

30のGo言語チュートリアルレッスンをすべて完了しました!知識体系全体を振り返りましょう:

フェーズ1 — Go基礎(レッスン1-6)

レッスン コアポイント
Go入門 Goのポジショニング:クラウドネイティブ時代のシステムレベル言語
変数と型 :=短い変数、ゼロ値メカニズム、iota列挙型
制御フロー forは唯一のループ、defer遅延実行、switchのfallthroughなし
関数 複数戻り値、クロージャ、init関数、関数は第一級市民
配列とスライス スライスは参照型、append拡張メカニズム、内部原理
Map comma okパターン、順序なし性質、スライスとの選択基準

フェーズ2 — 構造体とインターフェース(レッスン7-12)

レッスン コアポイント
構造体 値型 vs ポインタ、構造体タグ、匿名フィールド
メソッド 値レシーバ vs ポインタレシーバ、継承ではなくコンポジション
インターフェース 暗黙の実装(ダックタイピング)、型アサーション、インターフェース合成
エラー処理 errorインターフェース、errors.Is/As、カスタムエラー、明示的エラー処理哲学
パッケージとモジュール go mod、エクスポートルール、internalパッケージ
練習 構造体 + インターフェース + エラー処理の総合使用

フェーズ3 — 並行プログラミング(レッスン13-18)

レッスン コアポイント
Goroutine 軽量コルーチン、sync.WaitGroup、リーク防止
Channel 非バッファ vs バッファ、close/range、方向制限
Select マルチプレクシング、タイムアウト制御、ファンイン/ファンアウトパターン
syncパッケージ Mutex/RWMutexOncesync.Map、レース検出
タスクスケジューラ context.Context、並行協調、タイムアウトリトライ
Webクローラー レート制限、重複排除、エラーリトライ、グレースフル終了

フェーズ4 — 標準ライブラリと実践スキル(レッスン19-24)

レッスン コアポイント
文字列処理 strings/strconvパッケージ、strings.Builder
ファイルIO osパッケージ、bufio効率的な読み書き、ディレクトリトラバーサル
JSON処理 Marshal/Unmarshal、構造体タグ、ストリーム処理
HTTPプログラミング net/httpサーバー、Handlerインターフェース、ミドルウェアパターン
テスト テーブル駆動テスト、httptest、ベンチマーク、カバレッジ
正規表現と日付 regexpパッケージ、timeフォーマットと解析、タイマー

フェーズ5 — 総合プロジェクト(レッスン25-30)

レッスン コアポイント
CLIツール flagパッケージ、cobraライブラリ、引数バリデーション
REST API RESTful設計、JSON処理、ミドルウェアチェーン
データベース database/sql、SQLiteドライバ、CRUDカプセル化
デプロイと最適化 クロスコンパイル、Dockerビルド、グレースフルシャットダウン
総合プロジェクト(パート1) アーキテクチャ設計、ディレクトリ構造、ビジネスロジック、ユニットテスト
総合プロジェクト(パート2) API完成、ミドルウェア統合、統合テスト、コンテナ化リリース

コアコンピテンシーチェックリスト

30のレッスンの後、以下を習得しました:

TEXT
✅ Go言語の基本構文と型システム
✅ オブジェクト指向プログラミング(構造体 + インターフェース)
✅ 並行プログラミング(Goroutine + Channel + sync)
✅ 標準ライブラリコアパッケージ(net/http、encoding/json、testingなど)
✅ RESTful API設計と開発
✅ データベース操作(SQLドライバ、CRUDカプセル化)
✅ テスト駆動開発(ユニットテスト、統合テスト、ベンチマーク)
✅ プロジェクトエンジニアリング(ディレクトリ構造、依存管理、設定管理)
✅ コンテナ化デプロイ(Docker、マルチステージビルド、ヘルスチェック)

📝 今後の学習提案

上級方向

方向 推奨リソース 説明
Webフレームワーク GinEcho 本番グレードのHTTPフレームワーク
ORM GORMsqlx データベース操作の簡素化
マイクロサービス go-kitKratosgo-zero マイクロサービスフレームワーク
設定管理 Viper マルチフォーマット設定読み取り
ロギング zapzerolog 高性能ログライブラリ
APIゲートウェイ KongTraefik トラフィック管理
クラウドネイティブ Kubernetes、Docker Compose、Helm コンテナオーケストレーション
メッセージキュー NATS、Kafka、RabbitMQ 非同期通信

推奨書籍

  1. 「The Go Programming Language」 — Donovan & Kernighan
  2. 「Go in Action」 — William Kennedy
  3. 「Concurrency in Go」 — Katherine Cox-Buday
  4. 「100 Go Mistakes and How to Avoid Them」 — Teiva Harsanyi

推奨オンラインリソース

プロジェクト実践提案

難易度 プロジェクト 実践ポイント
⭐⭐ URL短縮サービス HTTP、JSON、SQLite
⭐⭐⭐ チャットルーム WebSocket、Goroutine、Channel
⭐⭐⭐ 個人ブログシステム テンプレートエンジン、セッション、ファイルアップロード
⭐⭐⭐⭐ 分散クローラー マルチノード協調、メッセージキュー、重複排除
⭐⭐⭐⭐ APIゲートウェイ ミドルウェアチェーン、負荷分散、レート制限とサーキットブレーカー

🎉 結び

"Less is more" — Goの設計哲学

30のレッスン、fmt.Println("Hello, World!")からデプロイ可能なRESTful APIサービスの構築まで、Go言語の学習旅程全体を完了しました。

Goの魅力はそのシンプルさにあります。あまりにも多くの選択肢を与えることはありませんが、すべての選択肢は慎重に検討されています。並行問題に直面したときはgoroutinechannelを、エラーを処理するときは「暗黙ではなく明示的」を、インターフェースを設計するときは「ダックタイピング」と「継承ではなくコンポジション」を考えましょう。

プログラミングは職人芸であり、学習の最良の方法はコードを書くことです。30のレッスンのサンプルをすべて打ち込み、すべての演習を完了し、その後、実際の小さなプロジェクトを見つけて練習してください。Goが想像していたよりもシンプルで強力であることに気づくでしょう。

TEXT
  ╔══════════════════════════════════════════════════╗
  ║   30レッスンのGo言語チュートリアルを              ║
  ║   完了しました!                                  ║
  ║   本番グレードのGoアプリケーションを開発する      ║
  ║   基礎を身につけました。                          ║
  ║   素晴らしいものを作りましょう!                    ║
  ╚══════════════════════════════════════════════════╝

次のレッスン

これは最後のレッスンです。コースの出発点に戻りたい場合は、レッスン1:Go入門に戻ってください。

ご質問やご意見がある場合は、GitHub Issuesでお気軽にご連絡ください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%