golang-backend-development について
Go のバックエンド開発者向けスキルガイドです。並行処理、Web サーバー、データベース連携、デプロイのパターンをコード例とともに解説します。
主な機能
- 01goroutine と channel の並行処理パターン
- 02HTTP サーバーと REST API
- 03Context によるタスク管理
- 04ワーカープールとデータパイプライン
- 05データベース接続管理
活用シーン
- 01並行処理対応の HTTP サービス構築
- 02マイクロサービスと REST API の実装
- 03並列データ処理ジョブの作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ manutej/ luxor-claude-marketplace --skill golang-backend-developmentインストール後、Claude Code で/golang-backend-developmentを入力して読み込まれたか確認します。
同梱ファイルEXAMPLES.mdREADME.md
詳細
Go バックエンド開発
Go を使用して、本番環境に対応したバックエンドシステムを構築するための包括的なスキルです。ゴルーチン、チャネル、Web サーバー、データベース連携、マイクロサービスアーキテクチャ、デプロイパターンを習得し、スケーラブルで並行処理に対応したバックエンドアプリケーションを開発します。
このスキルを使用する場面
次のような場合にこのスキルを使用します。
- 高性能な Web サーバーや REST API を構築する
- gRPC または HTTP を使用したマイクロサービスアーキテクチャを開発する
- ゴルーチンとチャネルを使用した並行処理を実装する
- 高スループットを必要とするリアルタイムシステムを作成する
- コネクションプーリングを使用したデータベース連携アプリケーションを構築する
- コンテナー化したデプロイ向けのクラウドネイティブアプリケーションを開発する
- パフォーマンスが重要なバックエンドサービスを作成する
- サービスディスカバリーを使用した分散システムを構築する
- イベント駆動型アーキテクチャを実装する
- ネットワーク機能を備えた CLI ツールやシステムユーティリティを作成する
- リアルタイム通信向けの WebSocket サーバーを開発する
- 並行処理を行うワーカーを使用したデータ処理パイプラインを構築する
Go が特に優れている分野:
- ネットワークプログラミングと HTTP サービス
- 軽量なゴルーチンによる並行処理
- ガベージコレクションを備えたシステムレベルのプログラミング
- クロスプラットフォームコンパイル
- 迅速な開発を支える短いコンパイル時間
- 組み込みのテスト機能とベンチマーク機能
主な概念
1. ゴルーチン:軽量な並行処理
ゴルーチンは、Go ランタイムが管理する軽量スレッドです。最小限のオーバーヘッドで並行実行を可能にします。
主な特性:
- 非常に軽量(スタックは約 2KB から開始)
- ランタイムによって OS スレッド上に多重化される
- 数千から数百万のゴルーチンを並行して実行できる
- 統合されたスケジューラーによって協調的にスケジュールされる
基本的なゴルーチンのパターン:
func main() {
// Launch concurrent computation
go expensiveComputation(x, y, z)
anotherExpensiveComputation(a, b, c)
}
go キーワードを使うと、新しいゴルーチンが起動し、expensiveComputation を anotherExpensiveComputation と並行して実行できます。これは Go の並行処理モデルの基盤です。
一般的な用途:
- バックグラウンド処理
- API 呼び出しの並行実行
- データの並列処理
- リアルタイムのイベント処理
- サーバーでの接続処理
2. チャネル:安全な通信
チャネルを使うと、ゴルーチン間で型安全な通信ができ、多くの状況で明示的なロックが不要になります。
チャネルの種類:
// Unbuffered channel - synchronous communication
ch := make(chan int)
// Buffered channel - asynchronous up to buffer size
ch := make(chan int, 100)
// Read-only channel
func receive(ch <-chan int) { /* ... */ }
// Write-only channel
func send(ch chan<- int) { /* ... */ }
チャネルによる同期:
func computeAndSend(ch chan int, x, y, z int) {
ch <- expensiveComputation(x, y, z)
}
func main() {
ch := make(chan int)
go computeAndSend(ch, x, y, z)
v2 := anotherExpensiveComputation(a, b, c)
v1 := <-ch // Block until result available
fmt.Println(v1, v2)
}
このパターンでは、処理を続行する前に両方の計算が完了することを保証します。チャネルは通信と同期の両方を担います。
チャネルのパターン:
- プロデューサー・コンシューマー
- ファンアウト/ファンイン
- パイプラインの各ステージ
- タイムアウトとキャンセル
- セマフォとレート制限
3. Select 文:チャネルの多重化
select 文を使うと、複数のチャネル操作を多重化できます。チャネル用の switch 文に似ています。
タイムアウトの実装:
timeout := make(chan bool, 1)
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
select {
case <-ch:
// Read from ch succeeded
case <-timeout:
// Operation timed out
}
Context によるキャンセル:
select {
case result := <-resultCh:
return result
case <-ctx.Done():
return ctx.Err()
}
4. Context パッケージ:リクエスト単位の値
context.Context インターフェースは、API 境界をまたいで期限、キャンセルシグナル、リクエスト単位の値を管理します。
Context インターフェース:
type Context interface {
// Done returns a channel closed when work should be canceled
Done() <-chan struct{}
// Err returns why context was canceled
Err() error
// Deadline returns when work should be canceled
Deadline() (deadline time.Time, ok bool)
// Value returns request-scoped value
Value(key any) any
}
Context の作成:
// Background context - never canceled
ctx := context.Background()
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// With deadline
deadline := time.Now().Add(10 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
// With values
ctx = context.WithValue(parentCtx, key, value)
ベストプラクティス:
- context は常に最初のパラメーターとして渡します:
func DoSomething(ctx context.Context, ...) - キャンセル可能な context を作成したら、すぐに
defer cancel()を呼び出します - 呼び出しチェーン全体に context を伝播させます
- 長時間実行される処理では
ctx.Done()を確認します - context の値はリクエスト単位のデータにのみ使用し、オプションのパラメーターには使用しません
5. WaitGroup:ゴルーチンの調整
sync.WaitGroup は、複数のゴルーチンがすべて終了するまで待機します。
基本的なパターン:
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Do work
}(i)
}
wg.Wait() // Block until all goroutines complete
一般的な用途:
- 並列タスクの完了を待つ
- ワーカープールを調整する
- クリーンアップの完了を保証する
- シャットダウンを同期する
6. Mutex:共有状態の保護
共有状態が必要な場合は、sync.Mutex または sync.RWMutex を使って保護します。
Mutex のパターン:
var (
service map[string]net.Addr
serviceMu sync.Mutex
)
func RegisterService(name string, addr net.Addr) {
serviceMu.Lock()
defer serviceMu.Unlock()
service[name] = addr
}
func LookupService(name string) net.Addr {
serviceMu.Lock()
defer serviceMu.Unlock()
return service[name]
}
読み取りの多いワークロード向けの RWMutex:
var (
cache map[string]interface{}
cacheMu sync.RWMutex
)
func Get(key string) interface{} {
cacheMu.RLock()
defer cacheMu.RUnlock()
return cache[key]
}
func Set(key string, value interface{}) {
cacheMu.Lock()
defer cacheMu.Unlock()
cache[key] = value
}
7. 並行 Web サーバーパターン
Go で並行接続を処理するための標準的なパターンです。
for {
rw := l.Accept()
conn := newConn(rw, handler)
go conn.serve() // Handle each connection concurrently
}
受け入れた各接続はそれぞれ独自のゴルーチンで処理されるため、サーバーは何千もの並行接続にも効率的に対応できます。
Web サーバー開発
HTTP サーバーの基本
シンプルな HTTP サーバー:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe("localhost:8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello!")
}
リクエスト処理パターン
ハンドラー関数:
func handler(w http.ResponseWriter, r *http.Request) {
// Read request
method := r.Method
path := r.URL.Path
query := r.URL.Query()
// Write response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"message": "success"}`)
}
ハンドラー構造体:
type APIHandler struct {
db *sql.DB
logger *log.Logger
}
func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Access dependencies
h.logger.Printf("Request: %s %s", r.Method, r.URL.Path)
// Handle request
}
ミドルウェアパターン
ロギングミドルウェア:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
// Usage
http.Handle("/api/", loggingMiddleware(apiHandler))
認証ミドルウェア:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !isValidToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
ミドルウェアの連結:
handler := loggingMiddleware(authMiddleware(corsMiddleware(apiHandler)))
http.Handle("/api/", handler)
HTTP ハンドラーのコンテキスト
コンテキストを使用した HTTP リクエスト:
func handleSearch(w http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Check query parameter
query := req.FormValue("q")
if query == "" {
http.Error(w, "missing query", http.StatusBadRequest)
return
}
// Perform search with context
results, err := performSearch(ctx, query)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Render results
renderTemplate(w, results)
}
コンテキスト対応の HTTP リクエスト:
func httpDo(ctx context.Context, req *http.Request,
f func(*http.Response, error) error) error {
c := &http.Client{}
// Run request in goroutine
ch := make(chan error, 1)
go func() {
ch <- f(c.Do(req))
}()
// Wait for completion or cancellation
select {
case <-ctx.Done():
<-ch // Wait for f to return
return ctx.Err()
case err := <-ch:
return err
}
}
ルーティングパターン
カスタムルーター:
type Router struct {
routes map[string]http.HandlerFunc
}
func (r *Router) Handle(pattern string, handler http.HandlerFunc) {
r.routes[pattern] = handler
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if handler, ok := r.routes[req.URL.Path]; ok {
handler(w, req)
} else {
http.NotFound(w, req)
}
}
RESTful API の構成:
// GET /api/users
func listUsers(w http.ResponseWriter, r *http.Request) { /* ... */ }
// GET /api/users/:id
func getUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
// POST /api/users
func createUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
// PUT /api/users/:id
func updateUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
// DELETE /api/users/:id
func deleteUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
並行処理パターン
1. パイプラインパターン
パイプラインでは、チャネルで接続された複数のステージを通じてデータを処理します。
ジェネレーターステージ:
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
処理ステージ:
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
パイプラインの使用:
func main() {
// Set up pipeline
c := gen(2, 3)
out := sq(c)
// Consume output
for n := range out {
fmt.Println(n) // 4 then 9
}
}
バッファー付きジェネレーター(ゴルーチンは不要):
func gen(nums ...int) <-chan int {
out := make(chan int, len(nums))
for _, n := range nums {
out <- n
}
close(out)
return out
}
2. ファンアウト/ファンインパターン
複数のワーカーに作業を分配し、結果をまとめます。
ファンアウト:複数のワーカー:
func main() {
in := gen(2, 3, 4, 5)
// Fan out: distribute work across two goroutines
c1 := sq(in)
c2 := sq(in)
// Fan in: merge results
for n := range merge(c1, c2) {
fmt.Println(n)
}
}
マージ関数(ファンイン):
func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
// Start output goroutine for each input channel
output := func(c <-chan int) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// Close out once all outputs are done
go func() {
wg.Wait()
close(out)
}()
return out
}
3. 明示的なキャンセルパターン
Done チャネルによるキャンセル:
func sq(done <-chan struct{}, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-done:
return
}
}
}()
return out
}
キャンセルのブロードキャスト:
func main() {
done := make(chan struct{})
defer close(done) // Broadcast to all goroutines
in := gen(done, 2, 3, 4)
c1 := sq(done, in)
c2 := sq(done, in)
// Process subset of results
out := merge(done, c1, c2)
fmt.Println(<-out)
// done closed on return, canceling all pipeline stages
}
キャンセルを伴うマージ:
func merge(done <-chan struct{}, cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
output := func(c <-chan int) {
defer wg.Done()
for n := range c {
select {
case out <- n:
case <-done:
return
}
}
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
4. ワーカープールパターン
ワーカー数を固定する:
func handle(queue chan *Request) {
for r := range queue {
process(r)
}
}
func Serve(clientRequests chan *Request, quit chan bool) {
// Start handlers
for i := 0; i < MaxOutstanding; i++ {
go handle(clientRequests)
}
<-quit // Wait to exit
}
セマフォパターン:
var sem = make(chan int, MaxOutstanding)
func handle(r *Request) {
sem <- 1 // Acquire
process(r)
<-sem // Release
}
func Serve(queue chan *Request) {
for req := range queue {
go handle(req)
}
}
ゴルーチンの作成数を制限する:
func Serve(queue chan *Request) {
for req := range queue {
sem <- 1 // Acquire before creating goroutine
go func() {
process(req)
<-sem // Release
}()
}
}
5. クエリ競争パターン
複数のソースにクエリを送り、最初の結果を返します。
func Query(conns []Conn, query string) Result {
ch := make(chan Result)
for _, conn := range conns {
go func(c Conn) {
select {
case ch <- c.DoQuery(query):
default:
}
}(conn)
}
return <-ch
}
6. 並列処理の例
逐次的な MD5 計算:
func MD5All(root string) (map[string][md5.Size]byte, error) {
m := make(map[string][md5.Size]byte)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
m[path] = md5.Sum(data)
return nil
})
return m, err
}
パイプラインによる並列 MD5 計算:
type result struct {
path string
sum [md5.Size]byte
err error
}
func sumFiles(done <-chan struct{}, root string) (<-chan result, <-chan error) {
c := make(chan result)
errc := make(chan error, 1)
go func() {
defer close(c)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
// Start goroutine for each file
go func() {
data, err := ioutil.ReadFile(path)
select {
case c <- result{path, md5.Sum(data), err}:
case <-done:
}
}()
// Check for early cancellation
select {
case <-done:
return errors.New("walk canceled")
default:
return nil
}
})
select {
case errc <- err:
case <-done:
}
}()
return c, errc
}
func MD5All(root string) (map[string][md5.Size]byte, error) {
done := make(chan struct{})
defer close(done)
c, errc := sumFiles(done, root)
m := make(map[string][md5.Size]byte)
for r := range c {
if r.err != nil {
return nil, r.err
}
m[r.path] = r.sum
}
if err := <-errc; err != nil {
return nil, err
}
return m, nil
}
7. リーキーバッファーパターン
バッファーを効率的に再利用します。
var freeList = make(chan *Buffer, 100)
func server() {
for {
b := <-serverChan // Wait for work
process(b)
// Try to reuse buffer
select {
case freeList <- b:
// Buffer on free list
default:
// Free list full, GC will reclaim
}
}
}
データベース連携
接続管理
データベース接続プール:
import "database/sql"
func initDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(10 * time.Minute)
// Verify connection
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}
クエリパターン
単一行クエリ:
func getUser(db *sql.DB, userID int) (*User, error) {
user := &User{}
err := db.QueryRow(
"SELECT id, name, email FROM users WHERE id = $1",
userID,
).Scan(&user.ID, &user.Name, &user.Email)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user not found")
}
if err != nil {
return nil, err
}
return user, nil
}
複数行クエリ:
func listUsers(db *sql.DB) ([]*User, error) {
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
return nil, err
}
users = append(users, user)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
コンテキストを使用した挿入/更新:
func createUser(ctx context.Context, db *sql.DB, user *User) error {
query := "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"
err := db.QueryRowContext(ctx, query, user.Name, user.Email).Scan(&user.ID)
return err
}
トランザクション処理
func transferFunds(ctx context.Context, db *sql.DB, from, to int, amount decimal.Decimal) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback() // Rollback if not committed
// Debit from account
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
amount, from)
if err != nil {
return err
}
// Credit to account
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
amount, to)
if err != nil {
return err
}
return tx.Commit()
}
プリペアドステートメント
func insertUsers(db *sql.DB, users []*User) error {
stmt, err := db.Prepare("INSERT INTO users (name, email) VALUES ($1, $2)")
if err != nil {
return err
}
defer stmt.Close()
for _, user := range users {
_, err := stmt.Exec(user.Name, user.Email)
if err != nil {
return err
}
}
return nil
}
エラー処理
カスタムエラー型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
// Usage
if email == "" {
return &ValidationError{Field: "email", Message: "required"}
}
エラーのラップ
import "fmt"
func processData(data []byte) error {
err := validateData(data)
if err != nil {
return fmt.Errorf("process data: %w", err)
}
return nil
}
// Unwrapping
if errors.Is(err, ErrValidation) {
// Handle validation error
}
if errors.As(err, &validationErr) {
// Access ValidationError fields
}
センチネルエラー
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
// Usage
if errors.Is(err, ErrNotFound) {
http.Error(w, "Resource not found", http.StatusNotFound)
}
テスト
単体テスト
func TestGetUser(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
user, err := getUser(db, 1)
if err != nil {
t.Fatalf("getUser failed: %v", err)
}
if user.Name != "John Doe" {
t.Errorf("expected name John Doe, got %s", user.Name)
}
}
テーブル駆動テスト
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{"valid email", "[email protected]", false},
{"missing @", "userexample.com", true},
{"empty string", "", true},
{"missing domain", "user@", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("validateEmail(%q) error = %v, wantErr %v",
tt.email, err, tt.wantErr)
}
})
}
}
ベンチマーク
func BenchmarkConcurrentMap(b *testing.B) {
m := make(map[string]int)
var mu sync.Mutex
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
m["key"]++
mu.Unlock()
}
})
}
HTTP ハンドラーのテスト
func TestHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/api/users", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
body, _ := ioutil.ReadAll(resp.Body)
// Assert body content
}
本番環境向けパターン
グレースフルシャットダウン
func main() {
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// Start server in goroutine
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Graceful shutdown with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
設定管理
type Config struct {
ServerPort int `env:"PORT" envDefault:"8080"`
DBHost string `env:"DB_HOST" envDefault:"localhost"`
DBPort int `env:"DB_PORT" envDefault:"5432"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"30s"`
}
func loadConfig() (*Config, error) {
cfg := &Config{}
if err := env.Parse(cfg); err != nil {
return nil, err
}
return cfg, nil
}
構造化ロギング
import "log/slog"
func setupLogger() *slog.Logger {
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
}
func handler(w http.ResponseWriter, r *http.Request) {
logger := slog.With(
"method", r.Method,
"path", r.URL.Path,
"remote", r.RemoteAddr,
)
logger.Info("handling request")
// Process request
logger.Info("request completed", "status", 200)
}
ヘルスチェック
func healthHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Check database
if err := db.Ping(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "unhealthy",
"error": err.Error(),
})
return
}
// Check other dependencies...
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
})
}
}
レート制限
import "golang.org/x/time/rate"
func rateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
// Usage
limiter := rate.NewLimiter(rate.Limit(10), 20) // 10 req/sec, burst 20
handler := rateLimitMiddleware(limiter)(apiHandler)
パニックのリカバリー
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
マイクロサービスパターン
サービス構成
type UserService struct {
db *sql.DB
cache *redis.Client
logger *slog.Logger
}
func NewUserService(db *sql.DB, cache *redis.Client, logger *slog.Logger) *UserService {
return &UserService{
db: db,
cache: cache,
logger: logger,
}
}
func (s *UserService) GetUser(ctx context.Context, userID string) (*User, error) {
// Check cache first
if user, err := s.getFromCache(ctx, userID); err == nil {
return user, nil
}
// Query database
user, err := s.getFromDB(ctx, userID)
if err != nil {
return nil, err
}
// Update cache
go s.updateCache(context.Background(), user)
return user, nil
}
gRPC サービス
type server struct {
pb.UnimplementedUserServiceServer
db *sql.DB
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user := &pb.User{}
err := s.db.QueryRowContext(ctx,
"SELECT id, name, email FROM users WHERE id = $1",
req.GetId(),
).Scan(&user.Id, &user.Name, &user.Email)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
return user, nil
}
サービスディスカバリー
type ServiceRegistry struct {
services map[string][]string
mu sync.RWMutex
}
func (r *ServiceRegistry) Register(name, addr string) {
r.mu.Lock()
defer r.mu.Unlock()
r.services[name] = append(r.services[name], addr)
}
func (r *ServiceRegistry) Discover(name string) (string, error) {
r.mu.RLock()
defer r.mu.RUnlock()
addrs := r.services[name]
if len(addrs) == 0 {
return "", fmt.Errorf("service %s not found", name)
}
// Simple round-robin
return addrs[rand.Intn(len(addrs))], nil
}
サーキットブレーカー
type CircuitBreaker struct {
maxFailures int
timeout time.Duration
failures int
lastFailure time.Time
state string // closed, open, half-open
mu sync.Mutex
}
func (cb *CircuitBreaker) Call(fn func() error) error {
cb.mu.Lock()
if cb.state == "open" {
if time.Since(cb.lastFailure) > cb.timeout {
cb.state = "half-open"
} else {
cb.mu.Unlock()
return errors.New("circuit breaker open")
}
}
cb.mu.Unlock()
err := fn()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.failures++
cb.lastFailure = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = "open"
}
return err
}
cb.failures = 0
cb.state = "closed"
return nil
}
ベストプラクティス
1. ゴルーチン管理
- ゴルーチンのライフサイクルとクリーンアップを常に考慮します
- キャンセルを伝播させるにはコンテキストを使用します
- すべてのゴルーチンが終了できるようにして、ゴルーチンリークを防ぎます
- ループ内のクロージャーには注意し、値を明示的に渡します
アンチパターン:
for _, v := range values {
go func() {
fmt.Println(v) // All goroutines share same v
}()
}
正しい例:
for _, v := range values {
go func(val string) {
fmt.Println(val) // Each goroutine gets its own copy
}(v)
}
2. チャネルのベストプラクティス
- チャネルは受信側ではなく送信側で閉じます
- ゴルーチンリークを防ぐにはバッファー付きチャネルを使用します
- ノンブロッキング操作には、
defaultを含むselectの使用を検討します - 忘れないでください。閉じたチャネルへの送信はパニックを起こし、受信するとゼロ値が返されます
3. エラー処理
- エラーを返し、パニックは起こしません(本当に例外的な場合を除きます)
fmt.Errorf("%w", err)を使ってコンテキスト情報を付加し、エラーをラップします- プログラムから処理するエラーにはカスタムエラー型を使用します
- 十分なコンテキスト情報を添えてエラーをログに記録します
4. パフォーマンス
- 頻繁に割り当てられるオブジェクトには
sync.Poolを使用します - 最適化の前にプロファイリングします:
go test -bench . -cpuprofile=cpu.prof - 並行マップアクセスのパターンには
sync.Mapの使用を検討します - 容量がわかっている場合はバッファー付きチャネルを使用します
- ホットパスで不要な割り当てを避けます
5. コードの構成
project/
├── cmd/
│ └── server/
│ └── main.go # Application entry point
├── internal/
│ ├── api/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── middleware/ # HTTP middleware
├── pkg/
│ └── utils/ # Public utilities
├── migrations/ # Database migrations
├── config/ # Configuration files
└── docker/ # Docker files
6. セキュリティ
- すべての入力を検証します
- SQL クエリにはプリペアドステートメントを使用します
- レート制限を実装します
- 本番環境では HTTPS を使用します
- クライアントに送信するエラーメッセージをサニタイズします
- リソースの枯渇を防ぐため、コンテキストタイムアウトを使用します
- 適切な認証と認可を実装します
7. テスト
- テーブル駆動テストを作成します
- テストヘルパー関数では
t.Helper()を使用します - 外部依存関係をモックします
- HTTP ハンドラーのテストには
httptestを使用します - パフォーマンスが重要なコードにはベンチマークを作成します
- ビジネスロジックのテストカバレッジは 80% を超えることを目指します
よくある落とし穴
1. 競合状態
問題:
var service map[string]net.Addr
func RegisterService(name string, addr net.Addr) {
service[name] = addr // RACE CONDITION
}
func LookupService(name string) net.Addr {
return service[name] // RACE CONDITION
}
解決策:
var (
service map[string]net.Addr
serviceMu sync.Mutex
)
func RegisterService(name string, addr net.Addr) {
serviceMu.Lock()
defer serviceMu.Unlock()
service[name] = addr
}
2. ゴルーチンのリーク
問題:
func process() {
ch := make(chan int)
go func() {
ch <- expensive() // Blocks forever if no receiver
}()
// Returns without reading from ch
}
解決策:
func process() {
ch := make(chan int, 1) // Buffered channel
go func() {
ch <- expensive() // Won't block
}()
}
3. チャネルを閉じない
受信側は、これ以上値が送られてこないことを知る必要があります。
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out) // IMPORTANT: close when done
}()
return out
}
4. バッファなしチャネルでブロックする
// This will deadlock
ch := make(chan int)
ch <- 1 // Blocks forever - no receiver
v := <-ch
バッファ付きチャネルを使用するか、ゴルーチンを分けてください。
5. 同期されていないチャネル操作
c := make(chan struct{})
// RACE CONDITION
go func() { c <- struct{}{} }()
close(c)
適切な同期によって happens-before 関係を確保してください。
リソースと参考資料
公式ドキュメント
- Go ドキュメント:https://go.dev/doc/
- Effective Go:https://go.dev/doc/effective_go
- Go ブログ:https://go.dev/blog/
- Go by Example:https://gobyexample.com/
並行処理の資料
- Go の並行処理パターン:https://go.dev/blog/pipelines
- Context パッケージ:https://go.dev/blog/context
- 通信によるメモリー共有:https://go.dev/blog/codelab-share
標準ライブラリー
- net/http:https://pkg.go.dev/net/http
- database/sql:https://pkg.go.dev/database/sql
- context:https://pkg.go.dev/context
- sync:https://pkg.go.dev/sync
ツール
- レース検出器:
go test -race - プロファイラー:
go tool pprof - ベンチマーク:
go test -bench - 静的解析:
go vet,staticcheck
スキルのバージョン:1.0.0 最終更新日:2025年10月 スキルのカテゴリー:バックエンド開発、システムプログラミング、並行プログラミング 前提条件:基本的なプログラミング知識、HTTP の理解、コマンドラインの使用経験 次におすすめのスキル:docker-deployment、kubernetes-orchestration、grpc-microservices