Go言語学習もついに28日目!
30分続けてきたものもあと、2日
(時より休むこともありましたが、、、)
ここまで学んできたPOST・GET APIを実用的な設計に近づけるためのコード整理を行いました。
今日のテーマ
- 処理の関数分割
- ログ出力の明確化
- エラー処理の一元化
- HTTPレスポンスの統一
🔧 整理したポイントまとめ
1. 関数分割:ハンドラ・ロジック・ユーティリティを明確に分離
// ハンドラ関数(ルーティング) // ビジネスロジック(Put/Get) // DynamoDBクライアント初期化
これにより、各関数の責務が明確になって、読みやすく、保守しやすくなりました!
2. エラーハンドリング:一貫したレスポンス形式を導入
共通のエラー応答関数を用意:
func respondError(w http.ResponseWriter, code int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(map[string]string{"error": msg}) }
どのエラーでも JSON で返るように統一しました。
3. ログ出力:エラーや処理開始・終了を明示
log.Printf("📥 受信: POST /items") log.Printf("⚠ エラー: %v", err)
処理の流れをログで追えるようになり、デバッグしやすさが段違いになりました!
4. HTTPレスポンスの改善:ステータスコードも意識
- 登録成功 →
201 Created
- データなし →
404 Not Found
- リクエスト不備 →
400 Bad Request
- 内部エラー →
500 Internal Server Error
💻 改良後のコード
package main import ( "context" "encoding/json" "fmt" "log" "net/http" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) type Item struct { UserID string `json:"UserID" dynamodbav:"UserID"` Name string `json:"Name" dynamodbav:"Name"` Age int `json:"Age" dynamodbav:"Age"` SortKey string `json:"-" dynamodbav:"sortKey"` // 常に "User" } var svc *dynamodb.Client func main() { svc = initDynamoClient() http.HandleFunc("/items", handlePostItem) http.HandleFunc("/items/", handleGetItem) fmt.Println("🚀 サーバー起動中:http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) } // POST /items func handlePostItem(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { respondError(w, http.StatusMethodNotAllowed, "Method not allowed") return } log.Println("📥 POST /items 受信") var item Item if err := json.NewDecoder(r.Body).Decode(&item); err != nil { respondError(w, http.StatusBadRequest, "リクエスト形式が不正です") return } if item.UserID == "" || item.Name == "" { respondError(w, http.StatusBadRequest, "UserIDとNameは必須です") return } item.SortKey = "User" if err := putItemToDynamo(item); err != nil { respondError(w, http.StatusInternalServerError, "データ登録に失敗しました") return } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"message": "登録成功"}) } // GET /items/{UserID} func handleGetItem(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { respondError(w, http.StatusMethodNotAllowed, "Method not allowed") return } id := strings.TrimPrefix(r.URL.Path, "/items/") if id == "" { respondError(w, http.StatusBadRequest, "IDが指定されていません") return } log.Printf("🔍 GET /items/%s", id) item, err := getItemFromDynamo(id) if err != nil { respondError(w, http.StatusInternalServerError, "DynamoDBからの取得に失敗しました") return } if item == nil { respondError(w, http.StatusNotFound, "データが見つかりません") return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(item) } // DynamoDBに登録 func putItemToDynamo(item Item) error { av, err := attributevalue.MarshalMap(item) if err != nil { return err } _, err = svc.PutItem(context.TODO(), &dynamodb.PutItemInput{ TableName: aws.String("Go-Practice-Users"), Item: av, }) return err } // DynamoDBから取得 func getItemFromDynamo(id string) (*Item, error) { input := &dynamodb.GetItemInput{ TableName: aws.String("Go-Practice-Users"), Key: map[string]types.AttributeValue{ "UserID": &types.AttributeValueMemberS{Value: id}, "sortKey": &types.AttributeValueMemberS{Value: "User"}, }, } result, err := svc.GetItem(context.TODO(), input) if err != nil { return nil, err } if result.Item == nil { return nil, nil } var item Item if err := attributevalue.UnmarshalMap(result.Item, &item); err != nil { return nil, err } return &item, nil } // 共通エラーレスポンス func respondError(w http.ResponseWriter, code int, msg string) { log.Printf("⚠ エラー [%d]: %s", code, msg) w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(map[string]string{"error": msg}) } // クライアント初期化 func initDynamoClient() *dynamodb.Client { cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Fatalf("AWS設定の読み込みに失敗: %v", err) } return dynamodb.NewFromConfig(cfg) }
今日の学びポイント
28日目の要点
- 処理の構造を明確に分けると、コードの読みやすさが劇的に向上!
- エラーハンドリングが一元化されると、保守性・拡張性が高まる
- ログを入れることでAPIの流れが「見える化」されて安心感が増した
今回、「設計っぽさ」が出てきて、ちょっと開発者らしくなった気がしますね。 今までの書き捨てっぽいコードから一歩進んだ感触で、HTTPステータスコードやログも意識できるようになっていかなきゃな。。。 エラー処理って地味だけど、やっぱり大事ですね。
以上となります。引き続きよろしくお願いいたします。