Skip to content

実行コンテキスト(ExecutionContext)

Spineリクエストの中核。

概要

ExecutionContextは、Spineパイプライン全体で共有される要求スコープのコンテキストです。 HTTP要求が到着すると、TransportアダプターはExecutionContextを生成し、このコンテキストはパイプラインのすべてのステップを通過し、要求情報と実行状況を渡します。

Context階層

SpineはContextを階層的に分離します。これは、HTTP、Event Consumer、WebSocketを同じパイプラインモデルとして処理するための設計です。

なぜそう分けるか。

|階層担当|使用場所| |------|------|----------| | ContextCarrier | Go標準コンテキスト配信|どこでも | EventBusCarrier |ドメインイベント発行(core.EventBus)|コントローラー、コンシューマー | ExecutionContext |実行フロー制御|ルーター、パイプライン、インターセプター| | ControllerContext | ExecutionContextの読み取り専用Facade |コントローラー(インターセプター注入値を参照) | HttpRequestContext | HTTP入力の解釈HTTP ArgumentResolver | | ConsumerRequestContext |イベント入力の解釈Consumer ArgumentResolver | | WebSocketContext | WebSocket入力解析| WebSocket ArgumentResolver |

目標:HTTP、Event Consumer、WebSocketが同じパイプラインモデルを共有しながら、各プロトコルの特性に合った入力解析が可能になります。

ベースのインターフェース

ContextCarrier

Go 標準 context.Context を渡す最小契約です。

go
// core/context.go
type ContextCarrier interface {
    Context() context.Context
}

EventBusCarrier

ドメインイベントを発行するためのEventBusアクセス契約です。戻り型はcore.EventBusです。

go
// core/context.go
type EventBusCarrier interface {
    EventBus() EventBus
}

core.EventBusは、ドメインイベントを収集して実行後に​​一度に発行するための最小契約です。

go
// core/event_bus.go
type EventBus interface {
    Publish(events ...publish.DomainEvent)
    Drain() []publish.DomainEvent
}

: internal/event/publish.EventBuscore.EventBus のタイプ別名 (type EventBus = core.EventBus) で、内部実装がこのタイプを満たすように構成されます。

ExecutionContextインタフェース

パイプライン全体で使用される実行フロー制御用のインタフェースです。

go
// core/context.go
type ExecutionContext interface {
    ContextCarrier
    EventBusCarrier

    // HTTP リクエスト 情報 (Consumer/WebSocketでは 意味が 異なる)
    Method() string                    // HTTP: GET, POST... / Consumer: "EVENT" / WS: "WS"
    Path() string                      // HTTP: /users/123 / Consumer: EventName / WS: path
    Header(name string) string         // HTTP ヘッダー (Consumer, WSは 空文字列)
    
    // パラメータ アクセス
    Params() map[string]string         // Path parameters
    PathKeys() []string                // Path key 順序
    Queries() map[string][]string      // Query parameters
    
    // 内部 保存所
    Set(key string, value any)         // 値 保存
    Get(key string) (any, bool)        // 値 参照
}

メソッドの詳細

Context()

Go 標準 context.Context を返します。要求のキャンセル、タイムアウト、値の転送に使用されます。

go
func (e *echoContext) Context() context.Context {
    return e.reqCtx  // HTTP リクエストの context
}

EventBus()

リクエストスコープのEventBusを返します。 Controllerがドメインイベントを発行するときに使用されます。

go
func (c *echoContext) EventBus() publish.EventBus {
    return c.eventBus
}

Method() / Path()

HTTP リクエストのメソッドとパスを返します。 Consumer と WebSocket では異なる意味で使用されます。

go
// HTTP
ctx.Method()  // "GET"
ctx.Path()    // "/users/123/posts/456"

// Consumer
ctx.Method()  // "EVENT"
ctx.Path()    // "order.created" (EventName)

// WebSocket
ctx.Method()  // "WS"
ctx.Path()    // WebSocket パス

Params() / PathKeys()

Path パラメータ情報を提供します。

go
// Route: /users/:userId/posts/:postId
// Request: /users/123/posts/456

ctx.Params()    // {"userId": "123", "postId": "456"}
ctx.PathKeys()  // ["userId", "postId"]

PathKeys()は、パラメータの宣言順序を保証します。 Spineの順序ベースのバインディングに不可欠です。

Queries()

クエリパラメータを複数値の形式で返します。

go
// Request: /users?status=active&tag=go&tag=web

ctx.Queries()  // {"status": ["active"], "tag": ["go", "web"]}

Set() / Get()

パイプライン内で値を共有するリポジトリ。

go
// Routerで path params 保存
ctx.Set("spine.params", params)
ctx.Set("spine.pathKeys", keys)

// Adapterで ResponseWriter 保存
ctx.Set("spine.response_writer", NewEchoResponseWriter(c))

// Interceptorで 参照
rw, ok := ctx.Get("spine.response_writer")

ControllerContext インタフェース

Controller専用のContext Viewです。 ExecutionContextの読み取り専用Facadeで、Interceptorから注入した値をControllerで参照するための公式通路です。

go
// core/context.go
type ControllerContext interface {
    Get(key string) (any, bool)
}

実装

go
// internal/runtime/controller_ctx.go
type controllerCtxView struct {
    ec core.ExecutionContext
}

func NewControllerContext(ec core.ExecutionContext) core.ControllerContext {
    return controllerCtxView{ec: ec}
}

func (v controllerCtxView) Get(key string) (any, bool) {
    return v.ec.Get(key)
}

使用例

go
// Controllerで Interceptorが 注入した 値 参照
func (c *UserController) GetUser(ctx context.Context, cc core.ControllerContext, userId path.Int) User {
    authInfo, _ := cc.Get("auth.user")
    // ...
}

注意: pkg/spine/types.goにはCtxインタフェース(Get(key string) (any, bool))が定義されており、ユーザーコードからspine.Ctxにもアクセス可能です。

HttpRequestContext インタフェース

HTTP 専用拡張インターフェイスです。 HTTP ArgumentResolverで使用されます。

go
// core/context.go
type HttpRequestContext interface {
    ContextCarrier
    EventBusCarrier

    // 個別 パラメータ アクセス
    Param(name string) string          // 特定の path param
    Query(name string) string          // 特定の query param (最初の 値)
    Header(name string) string         // 特定の ヘッダー
    
    // 全体 ビュー アクセス
    Params() map[string]string         // すべて path params
    Queries() map[string][]string      // すべて query params
    Headers() map[string][]string      // すべて ヘッダー
    
    // Body バインディング
    Bind(out any) error                // JSON body → struct
    
    // Multipart
    MultipartForm() (*multipart.Form, error)
}

HttpRequestContextにはRequestContextは含まれていません。 ContextCarrierEventBusCarrierを直接埋め込みます。さらに、Headers() map[string][]stringメソッドが追加され、ヘッダーマップ全体にアクセスできます。

メソッドの詳細

Param() / Query()

個々のパラメータに便利にアクセスします。

go
// Route: /users/:id?page=1&size=20

ctx.Param("id")      // "123"
ctx.Query("page")    // "1"
ctx.Query("size")    // "20"
ctx.Query("missing") // "" (なければ 空文字列)

Bind()

HTTP bodyを構造体にバインドします。

go
// internal/resolver/dto_resolver.go
func (r *DTOResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) {
    httpCtx, ok := ctx.(core.HttpRequestContext)
    if !ok {
        return nil, fmt.Errorf("HTTP リクエスト コンテキストが ではありません")
    }

    valuePtr := reflect.New(parameterMeta.Type)

    if err := httpCtx.Bind(valuePtr.Interface()); err != nil {
        return nil, fmt.Errorf("DTO バインディング 失敗 (%s): %w", parameterMeta.Type.Name(), err)
    }

    return valuePtr.Elem().Interface(), nil
}

MultipartForm()

Multipart form データにアクセスします。ファイルアップロード処理に使用されます。

go
// internal/resolver/uploaded_files_resolver.go
func (r *UploadedFilesResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) {
    httpCtx, ok := ctx.(core.HttpRequestContext)
    if !ok {
        return nil, fmt.Errorf("HTTP リクエスト コンテキストが ではありません")
    }

    form, err := httpCtx.MultipartForm()
    if err != nil {
        return nil, err
    }
    // ...
}

ConsumerRequestContext インタフェース

Event Consumer 専用の拡張インタフェースです。

go
// core/context.go
type ConsumerRequestContext interface {
    ContextCarrier
    EventBusCarrier

    EventName() string    // イベント 名前 (例: "order.created")
    Payload() []byte      // イベント ペイロード (JSON など)
}

メソッドの詳細

EventName()

受信したイベントの名前を返します。

go
ctx.EventName()  // "order.created"

Payload()

イベントの生のペイロードを返します。

go
payload := ctx.Payload()  // []byte (JSON)

Consumer Resolverの例

go
// internal/event/consumer/resolver/dto_resolver.go
func (r *DTOResolver) Resolve(ctx core.ExecutionContext, meta resolver.ParameterMeta) (any, error) {
    consumerCtx, ok := ctx.(core.ConsumerRequestContext)
    if !ok {
        return nil, fmt.Errorf("ConsumerRequestContextが ではありません")
    }

    payload := consumerCtx.Payload()
    if payload == nil {
        return nil, fmt.Errorf("Payloadが 空のため DTOを 生成する ことが ありません")
    }

    dtoPtr := reflect.New(meta.Type)
    if err := json.Unmarshal(payload, dtoPtr.Interface()); err != nil {
        return nil, fmt.Errorf("DTO デシリアライズに 失敗しました: %w", err)
    }

    return dtoPtr.Elem().Interface(), nil
}

WebSocketContext インタフェース

WebSocket 専用の ExecutionContext 拡張です。 ExecutionContextを埋め込み、パイプラインの互換性を維持します。

go
// core/context.go
type WebSocketContext interface {
    ExecutionContext

    ConnID() string       // 接続 ID
    MessageType() int     // メッセージ 型 (Text, Binary など)
    Payload() []byte      // メッセージ ペイロード
}

WebSocket Resolverの例

go
// internal/ws/resolver/dto_resolver.go
func (r *DTOResolver) Resolve(ctx core.ExecutionContext, meta resolver.ParameterMeta) (any, error) {
    wsCtx, ok := ctx.(core.WebSocketContext)
    if !ok {
        return nil, fmt.Errorf("WebSocketContextが ではありません")
    }

    payload := wsCtx.Payload()
    if payload == nil {
        return nil, fmt.Errorf("Payloadが 空のため DTOを 生成する ことが ありません")
    }

    dtoPtr := reflect.New(meta.Type)
    if err := json.Unmarshal(payload, dtoPtr.Interface()); err != nil {
        return nil, fmt.Errorf("DTO デシリアライズ 失敗: %w", err)
    }

    return dtoPtr.Elem().Interface(), nil
}

Echoアダプタの実装

Spine は Echo を HTTP Transport レイヤーとして使用します。 echoContextExecutionContextHttpRequestContextの両方を実装します。

go
// internal/adapter/echo/context_impl.go
type echoContext struct {
    echo     echo.Context           // Echoの 元の コンテキスト
    reqCtx   context.Context        // リクエスト スコープ コンテキスト
    store    map[string]any         // 内部 保存所
    eventBus publish.EventBus       // イベント バス
}

func NewContext(c echo.Context) core.ExecutionContext {
    return &echoContext{
        echo:     c,
        reqCtx:   c.Request().Context(),
        store:    make(map[string]any),
        eventBus: publish.NewEventBus(),
    }
}

主な実装

Path Parameters

Routerがマッチングした結果を優先し、なければEchoの値を使用します。

go
func (e *echoContext) Param(name string) string {
    // Spine Routerが 保存した 値 優先
    if raw, ok := e.store["spine.params"]; ok {
        if m, ok := raw.(map[string]string); ok {
            if v, ok := m[name]; ok {
                return v
            }
        }
    }
    // Fallback to Echo
    return e.echo.Param(name)
}

Params() - 防御コピー

外部からソースマップを変更しないようにコピーを返します。 maps.Copyを使用してください。

go
func (e *echoContext) Params() map[string]string {
    if raw, ok := e.store["spine.params"]; ok {
        if m, ok := raw.(map[string]string); ok {
            // return a shallow copy to avoid mutation
            copyMap := make(map[string]string, len(m))
            maps.Copy(copyMap, m)
            return copyMap
        }
    }
    // Echoで 直接 構成
    names := e.echo.ParamNames()
    values := e.echo.ParamValues()
    params := make(map[string]string, len(names))
    for i, name := range names {
        if i < len(values) {
            params[name] = values[i]
        }
    }
    return params
}

Headers()

すべてのHTTPヘッダーをマップとして返​​します。

go
func (e *echoContext) Headers() map[string][]string {
    return e.echo.Request().Header
}

EventBus

リクエストスコープのEventBusを返します。

go
func (c *echoContext) EventBus() publish.EventBus {
    return c.eventBus
}

Consumerアダプタの実装

Event Consumer 用の Context 実装です。

go
// internal/event/consumer/request_context_impl.go
type ConsumerRequestContextImpl struct {
    ctx      context.Context
    msg      *Message
    eventBus publish.EventBus
    store    map[string]any
}

func NewRequestContext(
    ctx context.Context,
    msg *Message,
    eventBus publish.EventBus,
) core.ExecutionContext {
    return &ConsumerRequestContextImpl{
        ctx:      ctx,
        msg:      msg,
        eventBus: eventBus,
        store:    make(map[string]any),
    }
}

Consumer Contextの特殊動作

Consumer は HTTP ではないため、いくつかのメソッドが異なる動作をします。

go
func (c *ConsumerRequestContextImpl) Method() string {
    // Consumer 実行は HTTP Methodの概念がなく, ルーティング 区切りを ため "EVENT" 使用
    return "EVENT"
}

func (c *ConsumerRequestContextImpl) Path() string {
    // Consumer ルーティングで Pathは EventNameを そのままに 使用
    return c.msg.EventName
}

func (c *ConsumerRequestContextImpl) Header(key string) string {
    // Consumerには HTTP Header 概念は なし
    return ""
}

func (c *ConsumerRequestContextImpl) Params() map[string]string {
    // Consumerには Path Parameter 概念は なし
    return map[string]string{}
}

func (c *ConsumerRequestContextImpl) PathKeys() []string {
    // Consumerには Path Key 概念は なし
    return []string{}
}

func (c *ConsumerRequestContextImpl) Queries() map[string][]string {
    // Consumerには Query Parameter 概念は なし
    return map[string][]string{}
}

WebSocketアダプタの実装

WebSocket用のContext実装です。 core.WebSocketContextを実装します。

go
// internal/ws/context_impl.go
type WSExecutionContext struct {
    ctx         context.Context
    connID      string
    path        string
    messageType int
    payload     []byte
    eventBus    publish.EventBus
    store       map[string]any
}

func NewWSExecutionContext(
    ctx context.Context,
    connID string,
    path string,
    messageType int,
    payload []byte,
    eventBus publish.EventBus,
    sendFn func(int, []byte) error,
) core.WebSocketContext {
    ctx = context.WithValue(ctx, pkgws.SenderKey, &connSender{send: sendFn})

    return &WSExecutionContext{
        ctx:         ctx,
        connID:      connID,
        path:        path,
        messageType: messageType,
        payload:     payload,
        eventBus:    eventBus,
        store:       make(map[string]any),
    }
}

WebSocket Contextの特殊動作

go
func (w *WSExecutionContext) Method() string {
    return "WS"
}

func (w *WSExecutionContext) ConnID() string {
    return w.connID
}

func (w *WSExecutionContext) MessageType() int {
    return w.messageType
}

func (w *WSExecutionContext) Payload() []byte {
    return w.payload
}

func (w *WSExecutionContext) EventBus() core.EventBus {
    return w.eventBus
}

ArgumentResolverとContext

ArgumentResolver は ExecutionContext を受け取り、必要に応じてプロトコル固有の Context で型断言します。

go
// internal/resolver/argument.go
type ArgumentResolver interface {
    Supports(parameterMeta ParameterMeta) bool
    Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error)
}

HTTP Resolverの例

go
// internal/resolver/path_int_resolver.go
func (r *PathIntResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) {
    // HttpRequestContextに 型 アサーション
    httpCtx, ok := ctx.(core.HttpRequestContext)
    if !ok {
        return nil, fmt.Errorf("HTTP リクエスト コンテキストが ではありません")
    }

    raw, ok := httpCtx.Params()[parameterMeta.PathKey]
    if !ok {
        return nil, fmt.Errorf("path paramを 見つかりません. %s", parameterMeta.PathKey)
    }

    value, err := strconv.ParseInt(raw, 10, 64)
    if err != nil {
        return nil, err
    }

    return path.Int{Value: value}, nil
}

Consumer Resolverの例

go
// internal/event/consumer/resolver/event_name_resolver.go
func (r *EventNameResolver) Resolve(ctx core.ExecutionContext, meta resolver.ParameterMeta) (any, error) {
    // ConsumerRequestContextに 型 アサーション
    consumerCtx, ok := ctx.(core.ConsumerRequestContext)
    if !ok {
        return nil, fmt.Errorf("ConsumerRequestContextが ではありません")
    }

    name := consumerCtx.EventName()
    if name == "" {
        return nil, fmt.Errorf("EventNameを RequestContextで 見つかりません")
    }

    return name, nil
}

WebSocket Resolverの例

go
// internal/ws/resolver/dto_resolver.go
func (r *DTOResolver) Resolve(ctx core.ExecutionContext, meta resolver.ParameterMeta) (any, error) {
    wsCtx, ok := ctx.(core.WebSocketContext)
    if !ok {
        return nil, fmt.Errorf("WebSocketContextが ではありません")
    }

    payload := wsCtx.Payload()
    if payload == nil {
        return nil, fmt.Errorf("Payloadが 空のため DTOを 生成する ことが ありません")
    }

    dtoPtr := reflect.New(meta.Type)
    if err := json.Unmarshal(payload, dtoPtr.Interface()); err != nil {
        return nil, fmt.Errorf("DTO デシリアライズ 失敗: %w", err)
    }

    return dtoPtr.Elem().Interface(), nil
}

Common Resolverの例

StdContextResolverは、HTTP、Consumer、WebSocketの両方で動作します。

go
// internal/resolver/std_context_resolver.go
func (r *StdContextResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) {
    baseCtx := ctx.Context()
    bus := ctx.EventBus()
    if bus != nil {
        return context.WithValue(baseCtx, publish.PublisherKey, bus), nil
    }
    return baseCtx, nil
}

ControllerContext Resolver

ControllerContextResolver は、ExecutionContext を読み取り専用 ControllerContext でラップします。

go
// internal/resolver/controller_context_resolver.go
func (r *ControllerContextResolver) Resolve(ctx core.ExecutionContext, _ ParameterMeta) (any, error) {
    return runtime.NewControllerContext(ctx), nil
}

パイプラインでの使用

Router

go
// internal/router/router.go
func (r *DefaultRouter) Route(ctx core.ExecutionContext) (core.HandlerMeta, error) {
    for _, route := range r.routes {
        if route.Method != ctx.Method() {
            continue
        }
        
        ok, params, keys := matchPath(route.Path, ctx.Path())
        if !ok {
            continue
        }
        
        // マッチした 情報を Contextに 保存
        ctx.Set("spine.params", params)
        ctx.Set("spine.pathKeys", keys)
        
        return route.Meta, nil
    }
    return core.HandlerMeta{}, httperr.NotFound("ハンドラーが ありません.")
}

Pipeline - Executeフロー

go
// internal/pipeline/pipeline.go
func (p *Pipeline) Execute(ctx core.ExecutionContext) (finalErr error) {
    // 1. グローバル Interceptor PreHandle (ルーティング前)
    // 2. Routerが 実行 対象を 決定
    // 3. ルート Interceptor PreHandle
    // 4. ArgumentResolver チェーン 実行
    // 5. Controller Method 呼び出し (Invoker)
    // 6. ReturnValueHandler 処理
    // 7. PostExecutionHook (イベント 発行 など)
    // 8. ルート Interceptor PostHandle (逆順)
    // 9. グローバル Interceptor PostHandle (逆順)
    // 10. AfterCompletion (成功/失敗 無関係, 逆順)
}

Pipeline - ArgumentResolver 呼び出し

go
// internal/pipeline/pipeline.go
func (p *Pipeline) resolveArguments(ctx core.ExecutionContext, paramMetas []resolver.ParameterMeta) ([]any, error) {
    args := make([]any, 0, len(paramMetas))

    for _, paramMeta := range paramMetas {
        resolved := false

        for _, r := range p.argumentResolvers {
            if !r.Supports(paramMeta) {
                continue
            }

            // ExecutionContextを 直接 伝達
            // Resolver 内部で 必要した 型にに アサーション
            val, err := r.Resolve(ctx, paramMeta)
            if err != nil {
                return nil, err
            }

            args = append(args, val)
            resolved = true
            break
        }

        if !resolved {
            return nil, fmt.Errorf(
                "ArgumentResolverに parameterが ありません. %d (%s)",
                paramMeta.Index,
                paramMeta.Type.String(),
            )
        }
    }
    return args, nil
}

Interceptor

go
// interceptor/cors/cors.go
func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
    // ResponseWriter 取得
    rwAny, ok := ctx.Get("spine.response_writer")
    if !ok {
        return nil
    }
    rw := rwAny.(core.ResponseWriter)
    
    // リクエスト 情報 確認
    origin := ctx.Header("Origin")
    if origin != "" && i.isAllowedOrigin(origin) {
        rw.SetHeader("Access-Control-Allow-Origin", origin)
    }
    
    // Preflight 処理
    if ctx.Method() == "OPTIONS" {
        rw.WriteStatus(204)
        return core.ErrAbortPipeline
    }
    
    return nil
}

内部ストレージ規約

Set()/Get() で使用するキーには明確な規約があります。

Spine予約キー

キータイプ設定位置用途
spine.paramsmap[string]stringRouterPath parameter値
spine.pathKeys[]stringRouterパスキーの順序
spine.response_writercore.ResponseWriterアダプター応答出力

使用例

go
// ReturnValueHandlerで ResponseWriter 使用
func (h *JSONReturnHandler) Handle(value any, ctx core.ExecutionContext) error {
    rwAny, ok := ctx.Get("spine.response_writer")
    if !ok {
        return fmt.Errorf("ExecutionContext 内で ResponseWriterを 見つかりません.")
    }
    
    rw, ok := rwAny.(core.ResponseWriter)
    if !ok {
        return fmt.Errorf("ResponseWriter 型が正しくありません.")
    }
    
    return rw.WriteJSON(200, value)
}

EventBus統合

ExecutionContextcore.EventBusが統合されています。

Controllerでイベントを発行する

go
// cmd/demo/controller.go
func (c *UserController) CreateOrder(ctx context.Context, orderId path.Int) string {
    // context.Contextで EventBusを 取り出し イベント 発行
    publish.Event(ctx, OrderCreated{
        OrderID: orderId.Value,
        At:      time.Now(),
    })

    return "OK"
}

EventBus 注入フロー

go
// internal/resolver/std_context_resolver.go
func (r *StdContextResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) {
    baseCtx := ctx.Context()
    bus := ctx.EventBus()
    if bus != nil {
        // EventBusを context.Contextに 注入
        return context.WithValue(baseCtx, publish.PublisherKey, bus), nil
    }
    return baseCtx, nil
}

PostExecutionHookからイベントを解放

Pipeline 実行完了後に収集されたイベントを一度に放出します。

go
// internal/event/hook/post_execution.go
func (h *EventDispatchHook) AfterExecution(ctx core.ExecutionContext, results []any, err error) {
    if err != nil {
        return
    }

    events := ctx.EventBus().Drain()
    if len(events) == 0 {
        return
    }

    h.Dispatcher.Dispatch(ctx.Context(), events)
}

設計原則

1. ControllerはExecutionContextを知らない

コントローラはExecutionContextHttpRequestContextを直接受け取りません。代わりに、意味タイプ(path.Intquery.Valuesなど)、context.Context、および必要に応じてControllerContextとして値のみを受け取ります。

go
// ❌ アンチパターン
func (c *UserController) GetUser(ctx core.ExecutionContext) User

// ✓ Spine 方式
func (c *UserController) GetUser(ctx context.Context, userId path.Int) User

// ✓ Interceptor 注入 値は 必要な場合
func (c *UserController) GetUser(ctx context.Context, cc core.ControllerContext, userId path.Int) User

2. ResolverはExecutionContextを受け取り、必要な型に断言する

ArgumentResolverはExecutionContextを受け取ります。プロトコル固有の機能が必要な場合は、HttpRequestContextConsumerRequestContext、またはWebSocketContextとタイプします。

go
func (r *PathIntResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) {
    httpCtx, ok := ctx.(core.HttpRequestContext)
    if !ok {
        return nil, fmt.Errorf("HTTP リクエスト コンテキストが ではありません")
    }
    // ...
}

3. シングルパイプライン、マルチプロトコル

HTTP、Event Consumer、WebSocketは同じパイプライン構造を共有します。コンテキスト層分離により、各プロトコルの特性をサポートしながら、コードの再利用を最大化します。

go
// HTTP Pipeline
httpPipeline.AddArgumentResolver(
    &resolver.StdContextResolver{},           // 共通
    &resolver.ControllerContextResolver{},    // 共通
    &resolver.HeaderResolver{},               // HTTP 専用
    &resolver.PathIntResolver{},              // HTTP 専用
    &resolver.PathStringResolver{},           // HTTP 専用
    &resolver.PathBooleanResolver{},          // HTTP 専用
    &resolver.PaginationResolver{},           // HTTP 専用
    &resolver.QueryValuesResolver{},          // HTTP 専用
    &resolver.DTOResolver{},                  // HTTP 専用
    &resolver.FormDTOResolver{},              // HTTP 専用
    &resolver.UploadedFilesResolver{},        // HTTP 専用
)

// Consumer Pipeline
consumerPipeline.AddArgumentResolver(
    &resolver.StdContextResolver{},           // 共通
    &eventResolver.EventNameResolver{},       // Consumer 専用
    &eventResolver.DTOResolver{},             // Consumer 専用
)

// WebSocket Pipeline
wsPipeline.AddArgumentResolver(
    &resolver.StdContextResolver{},           // 共通
    &wsResolver.ConnectionIDResolver{},       // WebSocket 専用
    &wsResolver.DTOResolver{},                // WebSocket 専用
)

まとめ

|インターフェース|役割|主な方法使用場所| |-----------|------|------------|----------| | ContextCarrier | Go context配信| Context() |どこでも | EventBusCarrier |イベント発行(core.EventBus)| EventBus() |コントローラー、コンシューマー | ExecutionContext |実行フロー制御| Method()Path()Header()Set()Get() |ルーター、パイプライン、インターセプター| | ControllerContext | ExecutionContext読み取り専用Facade | Get() | Controller | | HttpRequestContext | HTTP入力の解釈Param()Query()Header()Headers()Bind()MultipartForm() | HTTP ArgumentResolver | | ConsumerRequestContext |イベント入力の解釈EventName()Payload() | Consumer ArgumentResolver | | WebSocketContext | WebSocket入力解析| ConnID()MessageType()Payload() | WebSocket ArgumentResolver |

核心原則:Context階層分離により、HTTP、Event Consumer、WebSocketは同じパイプラインモデルを共有します。 Controllerは実行モデルをまったく知らず、ビジネスロジックだけに集中しています。