ホームページ > バックエンド開発 > Golang > Go マイクロサービスでのリクエスト、検証、レスポンス処理の改善

Go マイクロサービスでのリクエスト、検証、レスポンス処理の改善

Susan Sarandon
リリース: 2024-11-21 09:21:11
オリジナル
724 人が閲覧しました

Improving Request, Validation, and Response Handling in Go Microservices

このガイドでは、シンプルさ、再利用性、より保守しやすいコードベースを目指して、Go マイクロサービスでのリクエスト、検証、応答の処理をどのように合理化したかについて説明します。

導入

私はかなり長い間 Go でマイクロサービスを使ってきましたが、この言語が提供する明快さとシンプルさにいつも感謝しています。私が Go で最も気に入っている点の 1 つは、舞台裏では何も起こらないことです。コードは常に透過的で予測可能です。

ただし、開発の一部の部分は、特に API エンドポイントでの応答の検証と標準化に関しては、非常に面倒な場合があります。これに取り組むためにさまざまなアプローチを試してきましたが、最近、囲碁コースを書いているときに、かなり予想外のアイデアを思いつきました。このアイデアは私のハンドラーにちょっとした「魔法」を加えました。そして、驚いたことに、私はそれを気に入りました。このソリューションにより、リクエストの検証、デコード、パラメータ解析のためのすべてのロジックを一元化し、API のエンコードと応答を統一することができました。最終的に、コードの明瞭性の維持と反復的な実装の削減との間のバランスを見つけました。

問題

Go マイクロサービスを開発するときの一般的なタスクの 1 つは、受信した HTTP リクエストを効率的に処理することです。このプロセスには通常、リクエスト本文の解析、パラメータの抽出、データの検証、一貫した応答の送り返しが含まれます。例を挙げて問題を説明しましょう:

package main

import (
 "encoding/json"
 "github.com/go-chi/chi/v5"
 "github.com/go-chi/chi/v5/middleware"
 "github.com/go-playground/validator/v10"
 "log"
 "net/http"
)

type SampleRequest struct {
 Name string `json:"name" validate:"required,min=3"`
 Age  int    `json:"age" validate:"required,min=1"`
}

var validate = validator.New()

type ValidationErrors struct {
 Errors map[string][]string `json:"errors"`
}

func main() {
 r := chi.NewRouter()
 r.Use(middleware.Logger)
 r.Use(middleware.Recoverer)

 r.Post("/submit/{name}", func(w http.ResponseWriter, r *http.Request) {
  sampleReq := &SampleRequest{}

  // Set the path parameter
  name := chi.URLParam(r, "name")
  if name == "" {
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "name is required",
   })
   return
  }
  sampleReq.Name = name

  // Parse and decode the JSON body
  if err := json.NewDecoder(r.Body).Decode(sampleReq); err != nil {
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "Invalid JSON format",
   })
   return
  }

  // Validate the request
  if err := validate.Struct(sampleReq); err != nil {
   validationErrors := make(map[string][]string)
   for _, err := range err.(validator.ValidationErrors) {
    fieldName := err.Field()
    validationErrors[fieldName] = append(validationErrors[fieldName], err.Tag())
   }
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "Validation error",
    "body":    ValidationErrors{Errors: validationErrors},
   })
   return
  }

  // Send success response
  w.WriteHeader(http.StatusOK)
  json.NewEncoder(w).Encode(map[string]interface{}{
   "code":    http.StatusOK,
   "message": "Request received successfully",
   "body":    sampleReq,
  })
 })

 log.Println("Starting server on :8080")
 http.ListenAndServe(":8080", r)
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

手動で処理するハンドラー部分に焦点を当てて、上記のコードを説明します。

  • パス パラメーターを処理します: 必要なパス パラメーターが存在するかどうかを確認し、それを処理します。
  • リクエスト本文のデコード: 受信した JSON が正しく解析されていることを確認します。
  • 検証: バリデーター パッケージを使用して、リクエスト フィールドが要件基準を満たしているかどうかを確認します。
  • エラー処理: 検証が失敗した場合、または JSON の形式が不正な場合に、適切なエラー メッセージでクライアントに応答します。
  • 一貫した応答: 応答構造を手動で構築します。

コードは機能しますが、新しいエンドポイントごとに繰り返す必要がある大量の定型ロジックが含まれるため、保守が難しくなり、不整合が発生しやすくなります。

それでは、どうすればこれを改善できるでしょうか?

コードを分解する

この問題に対処し、コードの保守性を向上させるために、ロジックを リクエストレスポンス、および 検証の 3 つの異なる層に分割することにしました。このアプローチでは、各部分のロジックがカプセル化され、再利用可能になり、独立したテストが容易になります。

リクエスト層

Request レイヤーは、受信した HTTP リクエストからデータを解析して抽出する役割を果たします。このロジックを分離することで、データの処理方法を標準化し、すべての解析が均一に処理されるようにすることができます。

検証層

Validation レイヤーは、事前定義されたルールに従って解析されたデータを検証することだけに焦点を当てています。これにより、検証ロジックがリクエスト処理から分離され、さまざまなエンドポイント間での保守性と再利用性が向上します。

応答層

Response レイヤー ハンドラーは、応答の構築と書式設定を行います。応答ロジックを一元化することで、すべての API 応答が一貫した構造に従うようになり、デバッグが簡素化され、クライアントとの対話が改善されます。

つまり… コードをレイヤーに分割すると、再利用性テスト容易性保守性などの利点が得られますが、いくつかのトレードオフも伴います。複雑さが増すと、新しい開発者にとってプロジェクトの構造が理解しにくくなる可能性があり、単純なエンドポイントの場合、個別のレイヤーを使用するのは過剰に感じられ、オーバーエンジニアリングにつながる可能性があります。これらの長所と短所を理解することは、このパターンをいつ効果的に適用するかを決定するのに役立ちます。

一日の終わりに気になるのは、いつも自分が一番悩んでいることです。右?そこで、古いコードに手を加えて、上記のレイヤーの実装を開始しましょう。

コードをレイヤーにリファクタリングする

ステップ 1: リクエストレイヤーの作成

まず、コードをリファクタリングして、リクエスト解析を専用の関数またはモジュールにカプセル化します。この層はリクエスト本文の読み取りと解析のみに重点を置き、ハンドラー内の他の役割から確実に切り離されます。

新しいファイルを作成しますhttpsuite/request.go:

package main

import (
 "encoding/json"
 "github.com/go-chi/chi/v5"
 "github.com/go-chi/chi/v5/middleware"
 "github.com/go-playground/validator/v10"
 "log"
 "net/http"
)

type SampleRequest struct {
 Name string `json:"name" validate:"required,min=3"`
 Age  int    `json:"age" validate:"required,min=1"`
}

var validate = validator.New()

type ValidationErrors struct {
 Errors map[string][]string `json:"errors"`
}

func main() {
 r := chi.NewRouter()
 r.Use(middleware.Logger)
 r.Use(middleware.Recoverer)

 r.Post("/submit/{name}", func(w http.ResponseWriter, r *http.Request) {
  sampleReq := &SampleRequest{}

  // Set the path parameter
  name := chi.URLParam(r, "name")
  if name == "" {
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "name is required",
   })
   return
  }
  sampleReq.Name = name

  // Parse and decode the JSON body
  if err := json.NewDecoder(r.Body).Decode(sampleReq); err != nil {
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "Invalid JSON format",
   })
   return
  }

  // Validate the request
  if err := validate.Struct(sampleReq); err != nil {
   validationErrors := make(map[string][]string)
   for _, err := range err.(validator.ValidationErrors) {
    fieldName := err.Field()
    validationErrors[fieldName] = append(validationErrors[fieldName], err.Tag())
   }
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "Validation error",
    "body":    ValidationErrors{Errors: validationErrors},
   })
   return
  }

  // Send success response
  w.WriteHeader(http.StatusOK)
  json.NewEncoder(w).Encode(map[string]interface{}{
   "code":    http.StatusOK,
   "message": "Request received successfully",
   "body":    sampleReq,
  })
 })

 log.Println("Starting server on :8080")
 http.ListenAndServe(":8080", r)
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

: この時点では、リフレクションを使用する必要がありました。おそらく私はもっと愚かで、もっと良い方法を見つけるのを待つしかないでしょう。 ?

もちろん、これをテストすることもできるので、テスト ファイル httpsuite/request_test.go:
を作成します。

package httpsuite

import (
 "encoding/json"
 "errors"
 "github.com/go-chi/chi/v5"
 "net/http"
 "reflect"
)

// RequestParamSetter defines the interface used to set the parameters to the HTTP request object by the request parser.
// Implementing this interface allows custom handling of URL parameters.
type RequestParamSetter interface {
 // SetParam assigns a value to a specified field in the request struct.
 // The fieldName parameter is the name of the field, and value is the value to set.
 SetParam(fieldName, value string) error
}

// ParseRequest parses the incoming HTTP request into a specified struct type, handling JSON decoding and URL parameters.
// It validates the parsed request and returns it along with any potential errors.
// The pathParams variadic argument allows specifying URL parameters to be extracted.
// If an error occurs during parsing, validation, or parameter setting, it responds with an appropriate HTTP status.
func ParseRequest[T RequestParamSetter](w http.ResponseWriter, r *http.Request, pathParams ...string) (T, error) {
 var request T
 var empty T

 defer func() {
  _ = r.Body.Close()
 }()

 if r.Body != http.NoBody {
  if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
   SendResponse[any](w, "Invalid JSON format", http.StatusBadRequest, nil)
   return empty, err
  }
 }

 // If body wasn't parsed request may be nil and cause problems ahead
 if isRequestNil(request) {
  request = reflect.New(reflect.TypeOf(request).Elem()).Interface().(T)
 }

 // Parse URL parameters
 for _, key := range pathParams {
  value := chi.URLParam(r, key)
  if value == "" {
   SendResponse[any](w, "Parameter "+key+" not found in request", http.StatusBadRequest, nil)
   return empty, errors.New("missing parameter: " + key)
  }

  if err := request.SetParam(key, value); err != nil {
   SendResponse[any](w, "Failed to set field "+key, http.StatusInternalServerError, nil)
   return empty, err
  }
 }

 // Validate the combined request struct
 if validationErr := IsRequestValid(request); validationErr != nil {
  SendResponse[ValidationErrors](w, "Validation error", http.StatusBadRequest, validationErr)
  return empty, errors.New("validation error")
 }

 return request, nil
}

func isRequestNil(i interface{}) bool {
 return i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil())
}
ログイン後にコピー
ログイン後にコピー

ご覧のとおり、Request レイヤーは Validation レイヤーを使用します。ただし、メンテナンスを容易にするためだけでなく、検証レイヤーも分離して使用したい場合があるため、コード内でレイヤーを分離しておきたいと考えています。

将来的には、ニーズに応じて、すべてのレイヤーを分離し、いくつかのインターフェイスを使用して相互依存関係を許可することを決定する可能性があります。

ステップ 2: 検証レイヤーの実装

リクエストの解析が分離されたら、検証ロジックを処理するスタンドアロンの検証関数またはモジュールを作成します。このロジックを分離することで、簡単にテストし、複数のエンドポイントに一貫した検証ルールを適用できます。

そのために、httpsuite/validation.go ファイルを作成しましょう:

package main

import (
 "encoding/json"
 "github.com/go-chi/chi/v5"
 "github.com/go-chi/chi/v5/middleware"
 "github.com/go-playground/validator/v10"
 "log"
 "net/http"
)

type SampleRequest struct {
 Name string `json:"name" validate:"required,min=3"`
 Age  int    `json:"age" validate:"required,min=1"`
}

var validate = validator.New()

type ValidationErrors struct {
 Errors map[string][]string `json:"errors"`
}

func main() {
 r := chi.NewRouter()
 r.Use(middleware.Logger)
 r.Use(middleware.Recoverer)

 r.Post("/submit/{name}", func(w http.ResponseWriter, r *http.Request) {
  sampleReq := &SampleRequest{}

  // Set the path parameter
  name := chi.URLParam(r, "name")
  if name == "" {
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "name is required",
   })
   return
  }
  sampleReq.Name = name

  // Parse and decode the JSON body
  if err := json.NewDecoder(r.Body).Decode(sampleReq); err != nil {
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "Invalid JSON format",
   })
   return
  }

  // Validate the request
  if err := validate.Struct(sampleReq); err != nil {
   validationErrors := make(map[string][]string)
   for _, err := range err.(validator.ValidationErrors) {
    fieldName := err.Field()
    validationErrors[fieldName] = append(validationErrors[fieldName], err.Tag())
   }
   w.WriteHeader(http.StatusBadRequest)
   json.NewEncoder(w).Encode(map[string]interface{}{
    "code":    http.StatusBadRequest,
    "message": "Validation error",
    "body":    ValidationErrors{Errors: validationErrors},
   })
   return
  }

  // Send success response
  w.WriteHeader(http.StatusOK)
  json.NewEncoder(w).Encode(map[string]interface{}{
   "code":    http.StatusOK,
   "message": "Request received successfully",
   "body":    sampleReq,
  })
 })

 log.Println("Starting server on :8080")
 http.ListenAndServe(":8080", r)
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

次に、テスト ファイル httpsuite/validation_test.go:
を作成します。

package httpsuite

import (
 "encoding/json"
 "errors"
 "github.com/go-chi/chi/v5"
 "net/http"
 "reflect"
)

// RequestParamSetter defines the interface used to set the parameters to the HTTP request object by the request parser.
// Implementing this interface allows custom handling of URL parameters.
type RequestParamSetter interface {
 // SetParam assigns a value to a specified field in the request struct.
 // The fieldName parameter is the name of the field, and value is the value to set.
 SetParam(fieldName, value string) error
}

// ParseRequest parses the incoming HTTP request into a specified struct type, handling JSON decoding and URL parameters.
// It validates the parsed request and returns it along with any potential errors.
// The pathParams variadic argument allows specifying URL parameters to be extracted.
// If an error occurs during parsing, validation, or parameter setting, it responds with an appropriate HTTP status.
func ParseRequest[T RequestParamSetter](w http.ResponseWriter, r *http.Request, pathParams ...string) (T, error) {
 var request T
 var empty T

 defer func() {
  _ = r.Body.Close()
 }()

 if r.Body != http.NoBody {
  if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
   SendResponse[any](w, "Invalid JSON format", http.StatusBadRequest, nil)
   return empty, err
  }
 }

 // If body wasn't parsed request may be nil and cause problems ahead
 if isRequestNil(request) {
  request = reflect.New(reflect.TypeOf(request).Elem()).Interface().(T)
 }

 // Parse URL parameters
 for _, key := range pathParams {
  value := chi.URLParam(r, key)
  if value == "" {
   SendResponse[any](w, "Parameter "+key+" not found in request", http.StatusBadRequest, nil)
   return empty, errors.New("missing parameter: " + key)
  }

  if err := request.SetParam(key, value); err != nil {
   SendResponse[any](w, "Failed to set field "+key, http.StatusInternalServerError, nil)
   return empty, err
  }
 }

 // Validate the combined request struct
 if validationErr := IsRequestValid(request); validationErr != nil {
  SendResponse[ValidationErrors](w, "Validation error", http.StatusBadRequest, validationErr)
  return empty, errors.New("validation error")
 }

 return request, nil
}

func isRequestNil(i interface{}) bool {
 return i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil())
}
ログイン後にコピー
ログイン後にコピー

ステップ 3: 応答層の構築

最後に、応答構築を別のモジュールにリファクタリングします。これにより、すべての応答が一貫した形式に従うようになり、アプリケーション全体での応答の管理とデバッグが容易になります。

ファイルhttpsuite/response.go:
を作成します。

package httpsuite

import (
 "bytes"
 "context"
 "encoding/json"
 "errors"
 "fmt"
 "github.com/go-chi/chi/v5"
 "github.com/stretchr/testify/assert"
 "log"
 "net/http"
 "net/http/httptest"
 "strconv"
 "strings"
 "testing"
)

// TestRequest includes custom type annotation for UUID
type TestRequest struct {
 ID   int    `json:"id" validate:"required"`
 Name string `json:"name" validate:"required"`
}

func (r *TestRequest) SetParam(fieldName, value string) error {
 switch strings.ToLower(fieldName) {
 case "id":
  id, err := strconv.Atoi(value)
  if err != nil {
   return errors.New("invalid id")
  }
  r.ID = id
 default:
  log.Printf("Parameter %s cannot be set", fieldName)
 }

 return nil
}

func Test_ParseRequest(t *testing.T) {
 testSetURLParam := func(r *http.Request, fieldName, value string) *http.Request {
  ctx := chi.NewRouteContext()
  ctx.URLParams.Add(fieldName, value)
  return r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
 }

 type args struct {
  w          http.ResponseWriter
  r          *http.Request
  pathParams []string
 }
 type testCase[T any] struct {
  name    string
  args    args
  want    *TestRequest
  wantErr assert.ErrorAssertionFunc
 }
 tests := []testCase[TestRequest]{
  {
   name: "Successful Request",
   args: args{
    w: httptest.NewRecorder(),
    r: func() *http.Request {
     body, _ := json.Marshal(TestRequest{Name: "Test"})
     req := httptest.NewRequest("POST", "/test/123", bytes.NewBuffer(body))
     req = testSetURLParam(req, "ID", "123")
     req.Header.Set("Content-Type", "application/json")
     return req
    }(),
    pathParams: []string{"ID"},
   },
   want:    &TestRequest{ID: 123, Name: "Test"},
   wantErr: assert.NoError,
  },
  {
   name: "Missing body",
   args: args{
    w: httptest.NewRecorder(),
    r: func() *http.Request {
     req := httptest.NewRequest("POST", "/test/123", nil)
     req = testSetURLParam(req, "ID", "123")
     req.Header.Set("Content-Type", "application/json")
     return req
    }(),
    pathParams: []string{"ID"},
   },
   want:    nil,
   wantErr: assert.Error,
  },
  {
   name: "Missing Path Parameter",
   args: args{
    w: httptest.NewRecorder(),
    r: func() *http.Request {
     req := httptest.NewRequest("POST", "/test", nil)
     req.Header.Set("Content-Type", "application/json")
     return req
    }(),
    pathParams: []string{"ID"},
   },
   want:    nil,
   wantErr: assert.Error,
  },
  {
   name: "Invalid JSON Body",
   args: args{
    w: httptest.NewRecorder(),
    r: func() *http.Request {
     req := httptest.NewRequest("POST", "/test/123", bytes.NewBufferString("{invalid-json}"))
     req = testSetURLParam(req, "ID", "123")
     req.Header.Set("Content-Type", "application/json")
     return req
    }(),
    pathParams: []string{"ID"},
   },
   want:    nil,
   wantErr: assert.Error,
  },
  {
   name: "Validation Error for body",
   args: args{
    w: httptest.NewRecorder(),
    r: func() *http.Request {
     body, _ := json.Marshal(TestRequest{})
     req := httptest.NewRequest("POST", "/test/123", bytes.NewBuffer(body))
     req = testSetURLParam(req, "ID", "123")
     req.Header.Set("Content-Type", "application/json")
     return req
    }(),
    pathParams: []string{"ID"},
   },
   want:    nil,
   wantErr: assert.Error,
  },
  {
   name: "Validation Error for zero ID",
   args: args{
    w: httptest.NewRecorder(),
    r: func() *http.Request {
     body, _ := json.Marshal(TestRequest{Name: "Test"})
     req := httptest.NewRequest("POST", "/test/0", bytes.NewBuffer(body))
     req = testSetURLParam(req, "ID", "0")
     req.Header.Set("Content-Type", "application/json")
     return req
    }(),
    pathParams: []string{"ID"},
   },
   want:    nil,
   wantErr: assert.Error,
  },
 }

 for _, tt := range tests {
  t.Run(tt.name, func(t *testing.T) {
   got, err := ParseRequest[*TestRequest](tt.args.w, tt.args.r, tt.args.pathParams...)
   if !tt.wantErr(t, err, fmt.Sprintf("parseRequest(%v, %v, %v)", tt.args.w, tt.args.r, tt.args.pathParams)) {
    return
   }
   assert.Equalf(t, tt.want, got, "parseRequest(%v, %v, %v)", tt.args.w, tt.args.r, tt.args.pathParams)
  })
 }
}
ログイン後にコピー

テストファイルを作成しますhttpsuite/response_test.go:

package httpsuite

import (
 "errors"
 "github.com/go-playground/validator/v10"
)

// ValidationErrors represents a collection of validation errors for an HTTP request.
type ValidationErrors struct {
 Errors map[string][]string `json:"errors,omitempty"`
}

// NewValidationErrors creates a new ValidationErrors instance from a given error.
// It extracts field-specific validation errors and maps them for structured output.
func NewValidationErrors(err error) *ValidationErrors {
 var validationErrors validator.ValidationErrors
 errors.As(err, &validationErrors)

 fieldErrors := make(map[string][]string)
 for _, vErr := range validationErrors {
  fieldName := vErr.Field()
  fieldError := fieldName + " " + vErr.Tag()

  fieldErrors[fieldName] = append(fieldErrors[fieldName], fieldError)
 }

 return &ValidationErrors{Errors: fieldErrors}
}

// IsRequestValid validates the provided request struct using the go-playground/validator package.
// It returns a ValidationErrors instance if validation fails, or nil if the request is valid.
func IsRequestValid(request any) *ValidationErrors {
 validate := validator.New(validator.WithRequiredStructEnabled())
 err := validate.Struct(request)
 if err != nil {
  return NewValidationErrors(err)
 }
 return nil
}
ログイン後にコピー

このリファクタリングの各ステップでは、明確に定義されたレイヤーに特定の責任を委任することで、ハンドラー ロジックを簡素化できます。すべてのステップで完全なコードを示すわけではありませんが、これらの変更には、解析、検証、応答ロジックをそれぞれの関数またはファイルに移動することが含まれます。

サンプルコードのリファクタリング

ここで、レイヤーを使用するように古いコードを変更する必要があります。それがどのようになるかを見てみましょう。

package httpsuite

import (
 "github.com/go-playground/validator/v10"
 "testing"

 "github.com/stretchr/testify/assert"
)

type TestValidationRequest struct {
 Name string `validate:"required"`
 Age  int    `validate:"required,min=18"`
}

func TestNewValidationErrors(t *testing.T) {
 validate := validator.New()
 request := TestValidationRequest{} // Missing required fields to trigger validation errors

 err := validate.Struct(request)
 if err == nil {
  t.Fatal("Expected validation errors, but got none")
 }

 validationErrors := NewValidationErrors(err)

 expectedErrors := map[string][]string{
  "Name": {"Name required"},
  "Age":  {"Age required"},
 }

 assert.Equal(t, expectedErrors, validationErrors.Errors)
}

func TestIsRequestValid(t *testing.T) {
 tests := []struct {
  name           string
  request        TestValidationRequest
  expectedErrors *ValidationErrors
 }{
  {
   name:           "Valid request",
   request:        TestValidationRequest{Name: "Alice", Age: 25},
   expectedErrors: nil, // No errors expected for valid input
  },
  {
   name:    "Missing Name and Age below minimum",
   request: TestValidationRequest{Age: 17},
   expectedErrors: &ValidationErrors{
    Errors: map[string][]string{
     "Name": {"Name required"},
     "Age":  {"Age min"},
    },
   },
  },
  {
   name:    "Missing Age",
   request: TestValidationRequest{Name: "Alice"},
   expectedErrors: &ValidationErrors{
    Errors: map[string][]string{
     "Age": {"Age required"},
    },
   },
  },
 }

 for _, tt := range tests {
  t.Run(tt.name, func(t *testing.T) {
   errs := IsRequestValid(tt.request)
   if tt.expectedErrors == nil {
    assert.Nil(t, errs)
   } else {
    assert.NotNil(t, errs)
    assert.Equal(t, tt.expectedErrors.Errors, errs.Errors)
   }
  })
 }
}
ログイン後にコピー

ハンドラー コードをリクエストの解析、検証、応答の書式設定のためのレイヤーにリファクタリングすることにより、ハンドラー自体に以前埋め込まれていた反復的なロジックを削除することに成功しました。このモジュール式アプローチにより、読みやすさが向上するだけでなく、各責任に焦点を当てて再利用可能にすることで、保守性とテスト容易性も向上します。ハンドラーが簡素化されたことで、開発者はフロー全体に影響を与えることなく特定のレイヤーを簡単に理解し、変更できるようになり、よりクリーンでスケーラブルなコードベースを作成できます。

結論

専用のリクエスト、検証、および応答レイヤーを使用して Go マイクロサービスを構築するためのこのステップバイステップ ガイドが、よりクリーンで保守しやすいコードを作成するための洞察を提供できれば幸いです。このアプローチについてのご意見をぜひお聞きしたいです。何か重要なことを見逃しているでしょうか?あなた自身のプロジェクトでこのアイデアをどのように拡張または改善しますか?

ソース コードを調べて、プロジェクト内で httpsuite を直接使用することをお勧めします。このライブラリは、rluders/httpsuite リポジトリにあります。このライブラリをさらに堅牢で Go コミュニティにとって役立つものにするために、皆様のフィードバックと貢献が非常に貴重です。

次回でお会いしましょう。

以上がGo マイクロサービスでのリクエスト、検証、レスポンス処理の改善の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート