Go言語学習スケジュールもついに27日目!
あと3日の予定。
今日はDynamoDBに保存されたデータを「GETリクエストで取り出すAPI」を作りつつ、
その実装に必要な技術や考え方を一気に整理しました。
今日のゴール: DynamoDBの GetItem を使ってデータを ID 指定で取得し、 GoのGETエンドポイントからJSONで返す!
昨日はPutItem
GetItem APIの基本を理解
DynamoDBのGetItem
では、以下のようにキーを構造体ではなく map 形式で指定します:
input := &dynamodb.GetItemInput{ TableName: aws.String("Go-Practice-Users"), Key: map[string]types.AttributeValue{ "UserID": &types.AttributeValueMemberS{Value: "u001"}, "sortKey": &types.AttributeValueMemberS{Value: "User"}, }, }
データを構造体に変換:UnmarshalMap
取得結果は map[string]types.AttributeValue
の形式で返ってくるので、
attributevalue.UnmarshalMap()
を使ってGoの構造体に変換します:
var item Item
err := attributevalue.UnmarshalMap(result.Item, &item)
GETエンドポイントの作成 /items/{id}
Goの標準ライブラリだけでパスパラメータを処理する方法:
id := strings.TrimPrefix(r.URL.Path, "/items/")
この形式ならフレームワーク不要で /items/u001
のようなURLに対応可能!
存在しないIDへの対応
DynamoDBのGetItemは、該当データが存在しない場合でもエラーを返さず、
result.Item == nil
になるだけ。
if result.Item == nil { http.Error(w, "データが見つかりません", http.StatusNotFound) return }
HTTPレスポンスの整備
w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK)
エラー時は意味のあるHTTPステータスを返すことも重要ですね!
全体コード
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")として保存 } func main() { svc := initDynamoClient() http.HandleFunc("/items", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var item Item if err := json.NewDecoder(r.Body).Decode(&item); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if item.UserID == "" || item.Name == "" { http.Error(w, "UserIDとNameは必須です", http.StatusBadRequest) return } item.SortKey = "User" av, err := attributevalue.MarshalMap(item) if err != nil { http.Error(w, "Marshal失敗", http.StatusInternalServerError) return } _, err = svc.PutItem(context.TODO(), &dynamodb.PutItemInput{ TableName: aws.String("Go-Practice-Users"), Item: av, }) if err != nil { http.Error(w, "PutItem失敗", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"message":"Item saved"}`) }) // GET /items/{UserID} http.HandleFunc("/items/", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } userID := strings.TrimPrefix(r.URL.Path, "/items/") if userID == "" { http.Error(w, "UserIDが必要です", http.StatusBadRequest) return } input := &dynamodb.GetItemInput{ TableName: aws.String("Go-Practice-Users"), Key: map[string]types.AttributeValue{ "UserID": &types.AttributeValueMemberS{Value: userID}, "sortKey": &types.AttributeValueMemberS{Value: "User"}, }, } result, err := svc.GetItem(context.TODO(), input) if err != nil { http.Error(w, "GetItem失敗", http.StatusInternalServerError) return } if result.Item == nil { http.Error(w, "データが見つかりません", http.StatusNotFound) return } var item Item if err := attributevalue.UnmarshalMap(result.Item, &item); err != nil { http.Error(w, "Unmarshal失敗", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(item) }) fmt.Println("🚀 サーバー起動中:http://localhost:8080/items") log.Fatal(http.ListenAndServe(":8080", nil)) } func initDynamoClient() *dynamodb.Client { cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Fatalf("AWS設定の読み込みに失敗: %v", err) } return dynamodb.NewFromConfig(cfg) }
テスト方法(curl)
curl http://localhost:8080/items/u001
返ってくるJSON:
{ "UserID": "u001", "Name": "テスト太郎", "Age": 25 }
追加で気になったトピック
- クエリパラメータ vs パスパラメータの違い(/items?id=xxx との比較)
- gorilla/muxなどのルーティングライブラリの導入検討
シンプルなアプリでは標準ライブラリで充分ですが、複雑なルートが増えるとライブラリの導入した方が良いですね。 Webフレームワークなど、この一連の流れが終わった後に、やってみようかな。。
Godot?などにもチャレンジしたいですが。。。
以上となります。引き続きよろしくお願いいたします。