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

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 サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
GOの文字列操作:「文字列」パッケージのマスタリングGOの文字列操作:「文字列」パッケージのマスタリングMay 14, 2025 am 12:19 AM

GO言語で文字列パッケージをマスターすると、テキスト処理機能と開発効率が向上します。 1)コンテナ機能を使用してサブストリングを確認し、2)インデックス関数を使用してサブストリング位置を見つけ、3)関数を効率的にスプライスストリングスライス、4)機能を置き換えてサブストリングを置き換えます。空の文字列や大きな文字列操作のパフォーマンスの問題をチェックしないなど、一般的なエラーを避けるように注意してください。

「文字列」パッケージのヒントとトリックに移動します「文字列」パッケージのヒントとトリックに移動しますMay 14, 2025 am 12:18 AM

文字列の操作を簡素化し、コードをより明確かつ効率的にすることができるため、GOの文字列パッケージを気にする必要があります。 1)文字列を使用して、弦を効率的にスプライスするために参加します。 2)文字列を使用して、空白の文字で文字列を分割します。 3)文字列を介してサブストリング位置を見つけます。Indexと文字列lastindex; 4)文字列を使用して、文字列を置き換える。 5)文字列を使用して、ビルダーを効率的にスプライスします。 6)予期しない結果を避けるために、常に入力を確認してください。

Goの「文字列」パッケージ:文字列操作のためのあなたの頼みGoの「文字列」パッケージ:文字列操作のためのあなたの頼みMay 14, 2025 am 12:17 AM

theStringspackageIngoisESSENTINEFOREFFSTRINGMANIPULATION.1)ITOFFERSSSIMPLEYETPOWERFULFUNCTIONS FORTOSSCHECKINGSUBSTRINGSNINGSTRINGS.2)ITHANDLESUNICODEWELL、ITHANDLESUNICODEWELL

BYTESパッケージと文字列パッケージに移動します:どちらを使用すればよいですか?BYTESパッケージと文字列パッケージに移動します:どちらを使用すればよいですか?May 14, 2025 am 12:12 AM

whendeciding botedego'sbytespackageandstringspackage、usebytes.bufferbinarydataandstrings.builderforstringoperations.1)usebytes.bufferforkithbyteslices、binarydata、appendingdatatypes、およびwritioio.writioio.writioio.writioio.writioio.

「文字列」パッケージを使用して、ステップバイステップで文字列を操作する方法「文字列」パッケージを使用して、ステップバイステップで文字列を操作する方法May 13, 2025 am 12:12 AM

Goの文字列パッケージは、さまざまな文字列操作機能を提供します。 1)文字列を使用して、サブストリングを確認します。 2)文字列を使用して、ストリングをサブストリングスライスに分割します。 3)文字列を通して文字列をマージします。 4)文字列または文字列を使用して、文字列の最初と端でブランクまたは指定された文字を削除します。 5)指定されたすべてのサブストリングを文字列に置き換えます。ReplaceAll。 6)文字列を使用して、hasprefixまたは文字列hassuffixを使用して、文字列の接頭辞または接尾辞を確認します。

文字列パッケージに行く:私のコードを改善する方法は?文字列パッケージに行く:私のコードを改善する方法は?May 13, 2025 am 12:10 AM

GO言語文字列パッケージを使用すると、コードの品質が向上します。 1)文字列を使用して()join()を使用して、パフォーマンスのオーバーヘッドを避けるために、文字列アレイをエレガントに接続します。 2)strings.split()とstrings.contains()を組み合わせて、テキストを処理し、ケースの感度の問題に注意を払います。 3)文字列の乱用を避け、replace()を回避し、多数の置換に正規表現を使用することを検討します。 4)文字列を使用して、ビルダーを使用して、頻繁にスプライシング文字列の性能を向上させます。

GO BYTESパッケージで最も有用な機能は何ですか?GO BYTESパッケージで最も有用な機能は何ですか?May 13, 2025 am 12:09 AM

GoのBYTESパッケージは、バイトスライスを処理するためのさまざまな実用的な機能を提供します。 1.bites.containsは、バイトスライスに特定のシーケンスが含まれているかどうかを確認するために使用されます。 2.bites.splitは、バイトスライスをスモールピースに分割するために使用されます。 3.bites.joinは、複数のバイトスライスを1つに連結するために使用されます。 4.bites.trimspaceは、バイトスライスのフロントブランクとバックブランクを削除するために使用されます。 5.バイト。エクアルは、2つのバイトスライスが等しいかどうかを比較するために使用されます。 6.bytes.indexは、大規模なスライスでサブスライスの開始インデックスを見つけるために使用されます。

Goの「エンコーディング/バイナリ」パッケージを使用したバイナリデータ処理の習得:包括的なガイドGoの「エンコーディング/バイナリ」パッケージを使用したバイナリデータ処理の習得:包括的なガイドMay 13, 2025 am 12:07 AM

エンコード/binaryPackageIngoisESSENTINESTENTINESTINESTIDANDARDIZEDWAIDTOREADANDWRITEBINIRYDATA、クロスプラットフォームコンパティビティアンドハンドリングの可能性を確保することを確認します

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

SublimeText3 Linux 新バージョン

SublimeText3 Linux 新バージョン

SublimeText3 Linux 最新バージョン

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール