Skip to content

Request Context

The RequestContext in comby provides a structured container for metadata and execution parameters related to a specific Command or Query request. It is designed to enhance contextual awareness during execution, supporting distributed systems and event-driven architectures.

The RequestContext encapsulates details about the sender, including the tenant, identity, account, and session unique identifiers, which represent the entity initiating the request. These fields ensure traceability and accountability within multi-tenant systems.

Optional fields like TargetTenantUuid, TargetWorkspaceUuid, TargetIdentityUuid and TargetAggregateUuid specify the target entities for the request, allowing precise routing and processing. In particular, TargetIdentityUuid opts into a proxy / on-behalf-of authorization mode in which RBAC is evaluated against that identity in the target tenant instead of the sender. Execution parameters such as ExecuteTimeout, ExecuteWaitToFinish, and Attributes provide fine-grained control over how the request is handled. For example, these fields allow for skipping authorization checks (if using comby default), setting a timeout for command execution, or specifying whether the system should wait for command completion.

Traceability is further enhanced with the TraceId, a unique identifier for tracking the request across distributed systems. (Please note TraceId is not used yet). Developers can also use the MetaData map to store additional user-defined key-value pairs, offering flexibility for handling custom context-specific data.

The RequestContext can be instantiated using the NewRequestContext function, which initializes the MetaData field as an empty map. This map supports common data types like strings, integers, floats, and booleans, and can be manipulated using the SetMetaData, GetMetaData, and GetMetaDataMap methods. These methods enable developers to add, retrieve, or inspect custom metadata associated with a request.

By associating the RequestContext with commands and queries, comby ensures that requests carry all necessary context for accurate and efficient processing in distributed and multi-tenant environments. This design provides a balance of structure, traceability, and extensibility, making it easier to maintain and scale applications.

In comby an RequestContext is defined as an struct as follows:

go
type RequestContext struct {
	// sender context: who is executing the command
	SenderTenantUuid   string `json:"senderTenantUuid,omitempty"`
	SenderIdentityUuid string `json:"senderIdentityUuid,omitempty"`
	SenderAccountUuid  string `json:"senderAccountUuid,omitempty"`
	SenderSessionUuid  string `json:"senderSessionUuid,omitempty"`

	// (optional) what is the target aggregate for this request
	// Note: The TargetAggregateUuid is in the RequestContext because not every command
	// or query refers to a specific aggregate and therefore it would be wrong to put
	// the AggregateUuid in the interface.
	// Simple example: The list of tenants does not have an AggregateUuid to point to.
	// but retrieving a specific tenant does.
	TargetAggregateUuid string `json:"targetAggregateUuid,omitempty"`

	// (optional) what is the target aggregate version for this request.
	// TargetAggregateVersion is the current version of an aggregate that the command
	// is intended to update. This ensures the command operates on the correct model state.
	// If the model has been updated by another command (i.e., its version has changed),
	// the current command will be rejected to prevent conflicts. The user must then create a
	// new command using the latest model version.
	TargetAggregateVersion int64 `json:"targetAggregateVersion,omitempty"`

	// (optional) what is the target tenant for this request
	// TargetTenantUuid specifies which tenant the request is targeting,
	// which may differ from the sender's tenant in cross-tenant operations.
	TargetTenantUuid string `json:"targetTenantUuid,omitempty"`

	// (optional) what is the target workspace for this request
	// TargetWorkspaceUuid specifies which workspace the request is targeting.
	TargetWorkspaceUuid string `json:"targetWorkspaceUuid,omitempty"`

	// (optional) under which identity should this request be authorized.
	// When set, the auth middleware evaluates permissions against this identity
	// in the target tenant (cmd.GetTenantUuid()) instead of the sender's identity
	// in the sender's tenant. The Sender* fields keep representing the real
	// principal for audit and IsAuthenticated() purposes. Zero value preserves
	// the legacy behavior (sender-only RBAC).
	TargetIdentityUuid string `json:"targetIdentityUuid,omitempty"`

	// (optional) sets timeout for execution
	ExecuteTimeout int64 `json:"executeTimeout,omitempty"`

	// (optional) execute context: should the command wait for completion?
	// If set to true, the command can be waited for completion with Facade's 'WaitForCmd' method.
	ExecuteWaitToFinish bool `json:"executeWaitToFinish,omitempty"`

	// (optional) trace context: what is the trace id for this request
	TraceId int64 `json:"traceId,omitempty"`

	// (optional) additional user specific data
	Attributes *Attributes `json:"attributes,omitempty"`
}

Methods

IsAuthenticated

The IsAuthenticated() method checks whether the request context represents an authenticated request. It returns true if either a valid session is present (SenderSessionUuid is set) or the request originates from a service account (SenderIdentityUuid is set without a session).

go
func (rc *RequestContext) IsAuthenticated() bool

Usage

Example usage:

go
import "github.com/gradientzero/comby/v3"
import "github.com/gradientzero/comby/v3/domain/auth"

cmd := comby.NewCommand(...)
reqCtx := comby.NewRequestContext()
reqCtx.ExecuteWaitToFinish = true
reqCtx.Attributes.Set(auth.ExecuteSkipAuthorization, true)
cmd.SetReqCtx(reqCtx)

Example with workspace context:

go
reqCtx := comby.NewRequestContext()
reqCtx.SenderTenantUuid = tenantUuid
reqCtx.SenderIdentityUuid = identityUuid
reqCtx.TargetTenantUuid = targetTenantUuid
reqCtx.TargetWorkspaceUuid = workspaceUuid

if reqCtx.IsAuthenticated() {
    // proceed with authenticated request
    cmd.SetReqCtx(reqCtx)
}

Proxy / On-Behalf-Of Authorization

By default, the auth middleware evaluates RBAC against SenderIdentityUuid in SenderTenantUuid. For cross-tenant aggregator / bridge scenarios where a real principal needs to act under a proxy identity in another tenant, set TargetIdentityUuid on the request context to opt into a different evaluation mode:

  • Sender* fields always describe the real principal — audit trail, IsAuthenticated() and two-party-approval checks against SenderIdentityUuid stay anchored to the human/principal that initiated the request.
  • When TargetIdentityUuid is set, the auth middleware evaluates RBAC against that identity in the target tenant (cmd.GetTenantUuid() / qry.GetTenantUuid()). The default cross-tenant deny is bypassed for this path, so the bridge / target tenant's permission catalog applies.
  • When TargetIdentityUuid is empty, behavior is unchanged.
go
qry := comby.NewQuery(...)
reqCtx := qry.GetReqCtx()

// Sender stays at the real principal (audit + IsAuthenticated())
reqCtx.SenderTenantUuid   = realPrincipalTenantUuid
reqCtx.SenderIdentityUuid = realPrincipalIdentityUuid
reqCtx.SenderAccountUuid  = realPrincipalAccountUuid
reqCtx.SenderSessionUuid  = realPrincipalSessionUuid

// Target points at the bridge tenant and the proxy role inside it
reqCtx.TargetTenantUuid   = bridgeTenantUuid          // = qry.GetTenantUuid()
reqCtx.TargetIdentityUuid = bridgeProxyIdentityUuid   // authorized "as" this identity

The resulting event carries the full RequestContext, so audit consumers can answer both "who really acted" (Sender fields) and "under which role" (TargetIdentityUuid). The HistoryEventModel projection additionally surfaces TargetTenantUuid and TargetIdentityUuid for the same reason.