Go-DDDマーケットプレイスアプリケーション Echoフレームワーク実装の詳細解説¶
1. 概要¶
この文書では、Go-DDDマーケットプレイスアプリケーションにおけるEchoフレームワークの実装について詳しく解説します。Echoは高性能でミニマルなGoのWebフレームワークであり、RESTful APIの構築に適しています。
1.1 Echoフレームワークの役割¶
Echoフレームワークは以下の役割を担います:
- HTTPリクエスト/レスポンスの処理: クライアントからのHTTPリクエストを受け取り、適切なハンドラに転送し、レスポンスを返します。
- ルーティング: URLパスとHTTPメソッドに基づいて、リクエストを適切なハンドラ関数にマッピングします。
- ミドルウェア: リクエスト処理の前後に実行される機能(ロギング、認証、エラーハンドリングなど)を提供します。
- パラメータ取得: URLパス、クエリ文字列、リクエストボディからのパラメータ取得を簡素化します。
- レスポンス生成: JSON、XML、HTMLなど様々な形式でのレスポンス生成をサポートします。
1.2 アーキテクチャにおける位置づけ¶
以下の図は、Echoフレームワークのアーキテクチャにおける位置づけを示しています:
2. Echoフレームワークの実装¶
以下はEchoフレームワーク実装の概要を表すフロー図です:
2.1 基本設定¶
アプリケーションでは、Echoインスタンスの作成と基本設定を行います。
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
// Echoインスタンスの作成
e := echo.New()
// ミドルウェアの設定
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// サーバー起動
e.Start(":8080")
}
2.2 ルーティング設定¶
APIエンドポイントのルーティングを設定します。
package rest
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sklinkert/go-ddd/internal/application/services"
)
// SetupRouter configures the Echo router with all API endpoints
func SetupRouter(e *echo.Echo, productService *services.ProductService, sellerService *services.SellerService) {
// ミドルウェアの設定
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// APIバージョンプレフィックス
api := e.Group("/api/v1")
// 商品エンドポイント
NewProductController(api, productService)
// 出品者エンドポイント
NewSellerController(api, sellerService)
// ヘルスチェックエンドポイント
api.GET("/health", func(c echo.Context) error {
return c.JSON(200, map[string]string{"status": "ok"})
})
}
3. コントローラーの実装¶
3.1 商品コントローラー¶
商品関連のエンドポイントを処理するコントローラーを実装します。
package rest
import (
"github.com/labstack/echo/v4"
"github.com/sklinkert/go-ddd/internal/application/command"
"github.com/sklinkert/go-ddd/internal/application/services"
"net/http"
)
type ProductController struct {
productService *services.ProductService
}
func NewProductController(api *echo.Group, productService *services.ProductService) {
controller := &ProductController{
productService: productService,
}
api.POST("/products", controller.Create)
api.GET("/products", controller.FindAll)
api.GET("/products/:id", controller.ById)
api.PUT("/products/:id", controller.Update)
}
// Create handles POST /products
func (c *ProductController) Create(ctx echo.Context) error {
var req struct {
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
SellerId string `json:"seller_id"`
}
if err := ctx.Bind(&req); err != nil {
return ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
cmd := command.CreateProductCommand{
Name: req.Name,
Description: req.Description,
Price: req.Price,
SellerId: req.SellerId,
}
product, err := c.productService.Create(cmd)
if err != nil {
return ctx.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return ctx.JSON(http.StatusCreated, product)
}
// 他のハンドラメソッド(FindAll, ById, Update)も同様に実装
3.2 出品者コントローラー¶
出品者関連のエンドポイントを処理するコントローラーを実装します。
package rest
import (
"github.com/labstack/echo/v4"
"github.com/sklinkert/go-ddd/internal/application/command"
"github.com/sklinkert/go-ddd/internal/application/services"
"net/http"
)
type SellerController struct {
sellerService *services.SellerService
}
func NewSellerController(api *echo.Group, sellerService *services.SellerService) {
controller := &SellerController{
sellerService: sellerService,
}
api.POST("/sellers", controller.Create)
api.GET("/sellers", controller.FindAll)
api.GET("/sellers/:id", controller.ById)
api.PUT("/sellers/:id/name", controller.UpdateName)
api.PUT("/sellers/:id/email", controller.UpdateEmail)
api.DELETE("/sellers/:id", controller.Delete)
}
// Create handles POST /sellers
func (c *SellerController) Create(ctx echo.Context) error {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := ctx.Bind(&req); err != nil {
return ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
cmd := command.CreateSellerCommand{
Name: req.Name,
Email: req.Email,
}
seller, err := c.sellerService.Create(cmd)
if err != nil {
return ctx.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return ctx.JSON(http.StatusCreated, seller)
}
// 他のハンドラメソッド(FindAll, ById, UpdateName, UpdateEmail, Delete)も同様に実装
4. ミドルウェアの実装¶
4.1 エラーハンドリングミドルウェア¶
アプリケーション全体で一貫したエラーレスポンスを提供するミドルウェアを実装します。
package rest
import (
"github.com/labstack/echo/v4"
"github.com/sklinkert/go-ddd/internal/application/common"
"net/http"
)
// ErrorHandlerMiddleware はアプリケーションのエラーを適切なHTTPレスポンスに変換するミドルウェア
func ErrorHandlerMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
err := next(c)
if err == nil {
return nil
}
// エラーの種類に応じてHTTPステータスコードを決定
var statusCode int
var message string
// AppErrorの場合
if appErr, ok := err.(common.AppError); ok {
switch appErr.Type {
case common.NotFound:
statusCode = http.StatusNotFound
message = appErr.Message
case common.ValidationError:
statusCode = http.StatusBadRequest
message = appErr.Message
case common.DatabaseError:
statusCode = http.StatusInternalServerError
message = "データベースエラーが発生しました"
default:
statusCode = http.StatusInternalServerError
message = appErr.Message
}
} else if httpErr, ok := err.(*echo.HTTPError); ok {
// Echoの標準HTTPエラーの場合
statusCode = httpErr.Code
message = httpErr.Message.(string)
} else {
// その他のエラー
statusCode = http.StatusInternalServerError
message = "予期しないエラーが発生しました"
}
// JSONレスポンスを返す
return c.JSON(statusCode, map[string]string{"error": message})
}
}
4.2 認証ミドルウェア¶
JWTを使用した認証ミドルウェアを実装します。
package rest
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// SetupAuthMiddleware configures JWT authentication middleware
func SetupAuthMiddleware(e *echo.Echo) {
// JWT設定
config := middleware.JWTConfig{
SigningKey: []byte("your-secret-key"),
TokenLookup: "header:Authorization",
AuthScheme: "Bearer",
}
// 認証が必要なルートグループ
auth := e.Group("/api/v1/auth")
auth.Use(middleware.JWTWithConfig(config))
// 認証が必要なエンドポイントを追加
auth.GET("/profile", getProfile)
}
// getProfile handles GET /api/v1/auth/profile
func getProfile(c echo.Context) error {
// JWTからユーザー情報を取得
user := c.Get("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
userId := claims["id"].(string)
// ユーザープロファイルを返す
return c.JSON(http.StatusOK, map[string]string{
"id": userId,
"message": "認証済みユーザー",
})
}
5. リクエスト/レスポンスの処理¶
5.1 リクエストバインディング¶
クライアントからのリクエストデータを構造体にバインドする方法を示します。
// JSONリクエストのバインド例
func (c *ProductController) Create(ctx echo.Context) error {
var req struct {
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
SellerId string `json:"seller_id"`
}
if err := ctx.Bind(&req); err != nil {
return ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
// 以降の処理...
}
// URLパラメータの取得例
func (c *ProductController) ById(ctx echo.Context) error {
id := ctx.Param("id")
product, err := c.productService.ById(id)
if err != nil {
return ctx.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
return ctx.JSON(http.StatusOK, product)
}
// クエリパラメータの取得例
func (c *ProductController) FindAll(ctx echo.Context) error {
limit := ctx.QueryParam("limit")
offset := ctx.QueryParam("offset")
// limitとoffsetを整数に変換して使用
// ...
products, err := c.productService.FindAll()
if err != nil {
return ctx.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return ctx.JSON(http.StatusOK, products)
}
5.2 レスポンス生成¶
様々な形式でのレスポンス生成方法を示します。
```go // JSONレスポンス func (c *ProductController) ById(ctx echo.Context) error { // ... return ctx.JSON(http.StatusOK, product) }
// エラーレスポンス func (c *ProductController) Update(ctx echo.Context) error { // ... if err != nil { return ctx.JSON(http.StatusInternalServerError, map[string]string{ "error": err.Error(), }) } // ... }
// カスタムHTTPステータスコード func (c *ProductController) Create(ctx echo.Context) error { // ... return ctx.JSON(http.StatusCreated, product) }