GoでHTTPサーバーを構築: ミドルウェアとインメモリデータ管理を使ったデータ処理

## はじめに
前回の記事では、Go言語で基本的なHTTPサーバーの構築手順を解説しました。
今回の記事では、前回に引き続きリクエストを処理する際のミドルウェアの使用法、データベース接続の代わりにインメモリでデータを管理する方法を紹介します。
https://qiita.com/h-i-ist/items/ca33366060b47acc6fe8

## 1. プロジェクトの構成

```bash
go-http-server/
├── cmd/
│   └── app/
│       └── main.go
├── internal/
│   ├── handlers/
│   │   └── handler.go
│   └── middleware/
│       └── logging.go
├── storage/
│   └── memory.go
└── go.mod
```

### 各フォルダーの役割
前回の記事に加え、今回新たに **middleware**と **storage** ディレクトリを追加しています。

- **cmd/:**  アプリケーションのエントリーポイントとなるmain.goを配置。ここでサーバーの起動処理を行います。
- **internal/handlers/:**  HTTPリクエストに応じて動作するハンドラーを配置。各エンドポイントごとの処理が定義されます。
- **internal/middleware/:**  ロギングなど、全てのリクエストに対する共通処理を担当するミドルウェアを配置します。
- **storage/:**  データベース接続の代わりに、インメモリでデータを保持・管理するためのストレージ層を配置します。

## 2. 各ファイルの実装内容

### **cmd/app/main.go**
main.go では、HTTPサーバーを起動し、ミドルウェアやハンドラーを設定します。

```Go
package main

import (
    "log"
    "net/http"
    "example.com/go-http-server/internal/handlers"
    "example.com/go-http-server/internal/middleware"
    "example.com/go-http-server/storage"
)

func main() {
    // インメモリデータストアの初期化
    store := storage.NewInMemoryStore()　// メモリ内でユーザー情報を保持するストレージ

    // ミドルウェアとハンドラーの設定
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", handlers.HelloHandler)
    mux.HandleFunc("/users", handlers.UserHandler(store))

    // ミドルウェアの適用
    handlerWithMiddleware := middleware.Logging(mux)

    log.Println("Starting server on :8080...")
    log.Fatal(http.ListenAndServe(":8080", handlerWithMiddleware))
}
```

### **internal/handlers/handler.go**
handler.go には、HTTPリクエストに応じて動的なレスポンスを返すためのハンドラーを定義します。ここでは、クエリパラメータに基づいたレスポンスや、インメモリデータストアを用いたユーザー情報の登録・取得機能を実装します。

```Go
package handlers

import (
    "encoding/json" // JSONのエンコード/デコードに使用
    "fmt"           // フォーマットされたI/O操作を提供
    "net/http"      // HTTPリクエストとレスポンスを扱う
    "example.com/go-http-server/storage" // インメモリストレージをインポート
)

// HelloHandlerは、"/hello"エンドポイントにアクセスされたときに名前を返すシンプルなハンドラーです。
// クエリパラメータに基づいて動的にメッセージを変更します。
func HelloHandler(w http.ResponseWriter, r *http.Request) {
    // クエリパラメータから"name"を取得
    name := r.URL.Query().Get("name")
    if name == "" {
        name = "World" // パラメータがない場合はデフォルトで"World"
    }
    // レスポンスに"Hello, [name]!"を出力
    fmt.Fprintf(w, "Hello, %s!", name)
}

// UserHandlerは、"/users"エンドポイントでユーザー情報を管理するハンドラーです。
// インメモリストアを利用して、ユーザーの登録（POST）と取得（GET）を行います。
func UserHandler(store *storage.InMemoryStore) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        // GETリクエストの場合、登録されているすべてのユーザーを取得してJSONで返す
        case http.MethodGet:
            users := store.GetUsers() // インメモリストアからユーザーを取得
            json.NewEncoder(w).Encode(users) // ユーザー情報をJSON形式でエンコードしてレスポンスに書き込み
        // POSTリクエストの場合、新しいユーザーを登録
        case http.MethodPost:
            var user storage.User
            // リクエストボディをJSONとしてデコードし、User構造体にマッピング
            if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
                // デコードに失敗した場合は400 Bad Requestを返す
                http.Error(w, "Invalid input", http.StatusBadRequest)
                return
            }
            // インメモリストアに新しいユーザーを追加
            store.AddUser(user)
            // 成功時には201 Createdを返す
            w.WriteHeader(http.StatusCreated)
        // GET/POST以外のHTTPメソッドの場合、405 Method Not Allowedを返す
        default:
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        }
    }
}
```

### **internal/middleware/logging.go**
ミドルウェアでは、すべてのリクエストに対して共通の処理を定義します。
ここでは、受け取ったリクエストのメソッドとパスをログに記録します。

```Go
package middleware

import (
    "log"       // ログ出力を扱う標準ライブラリ
    "net/http"  // HTTPリクエストとレスポンスを扱う
)

// Loggingはミドルウェア関数です。この関数はすべてのHTTPリクエストのメソッドとURLパスをログに記録します。
// nextは次に呼び出されるハンドラーで、Logging関数はそのハンドラーをラップして処理を追加します。
func Logging(next http.Handler) http.Handler {
    // HTTPハンドラーをラップしてリクエストごとにログを記録
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // リクエストのメソッドとパスをログに出力
        log.Printf("Request: %s %s", r.Method, r.URL.Path)
        
        // ラップされたハンドラー（next）を呼び出して、通常のリクエスト処理を行う
        next.ServeHTTP(w, r)
    })
}
```

### **storage/memory.go**
storage/memory.goでは、インメモリのデータストアを実装します。
このストアは、データベースを使用せずにメモリ上でユーザー情報を保存・管理するための簡単な実装です。

```Go
package storage

// Userはユーザー情報を保持する構造体です。
// JSONのエンコード/デコード時にIDとNameが対応するフィールドとして扱われます。
type User struct {
    ID   int    `json:"id"`   // ユーザーID（自動生成）
    Name string `json:"name"` // ユーザー名（クライアントから提供される）
}

// InMemoryStoreはメモリ内でユーザー情報を管理するためのストアです。
// データベースの代わりに使い、サーバーのメモリ内にユーザー情報を保存します。
type InMemoryStore struct {
    users []User // ユーザー情報を格納するスライス
}

// NewInMemoryStoreは新しいInMemoryStoreを初期化して返します。
// このストアは、初期状態で空のユーザースライスを持っています。
func NewInMemoryStore() *InMemoryStore {
    return &InMemoryStore{users: []User{}} // 空のユーザースライスで初期化
}

// AddUserはInMemoryStoreに新しいユーザーを追加するメソッドです。
// 追加されるユーザーにはIDが自動的に割り当てられます。
func (s *InMemoryStore) AddUser(user User) {
    // 新しいユーザーのIDは、現在のユーザースライスの長さに基づいて設定
    user.ID = len(s.users) + 1
    // ユーザースライスに新しいユーザーを追加
    s.users = append(s.users, user)
}

// GetUsersはInMemoryStore内のすべてのユーザーを取得するメソッドです。
// 返される値は、現在メモリに格納されている全ユーザーのスライスです。
func (s *InMemoryStore) GetUsers() []User {
    return s.users // 登録されている全ユーザーを返す
}
```

## 3. プロジェクトの実行
プロジェクトが完成したら、以下のコマンドでサーバーを実行します。

```bash
go run cmd/app/main.go
```

サーバーが起動したら、次の手順で動作確認を行い、サーバーが正常に動作していることを確認します。
### 動作確認
ユーザーの登録（POSTリクエスト）

```bash
curl -X POST http://localhost:8080/users -d '{"name": "John"}' -H "Content-Type: application/json"
```

ユーザーの取得（GETリクエスト）

```bash
curl http://localhost:8080/users
```

## 終わりに
この記事では、Goの標準ライブラリを使ってHTTPサーバーを構築し、以下の内容を実装しました。

- ミドルウェアの導入: すべてのリクエストに対して共通の処理（ロギング）を行う仕組みを導入しました。
- インメモリデータ管理: データベースを使用せずに、メモリ上でユーザー情報を管理する方法を実装しました。

この記事を通じて、データベースを使わずにメモリ上でユーザー情報を管理できる簡単なサーバーを構築できました。次のステップとして、データベース（例: PostgreSQL、MongoDB）への接続や、セキュリティ面の強化を進めていきたいと思います。