You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This proposal introduces typed interceptors to Goa's design DSL. Interceptors provide a type-safe mechanism for injecting cross-cutting concerns into method execution, with clean interfaces for reading and modifying payloads and results. They can be defined at API, Service, and Method levels.
Requirements
Interceptors must be defined in the design
Interceptors must be fully typed
Interceptors can stop the request chain
Interceptors can modify the request payload
Interceptors can modify the response result
Interceptors can modify the request context
Interceptors can modify the error returned by the endpoint
Design
DSL Example
varAuditInterceptor=Interceptor("AuditInterceptor", func() {
Description("Adds audit information to requests and tracks response status")
// Define payload fields the interceptor needs to readReadPayload(func() {
Attribute("auth", String, "JWT auth token")
})
// Define payload fields the interceptor will writeWritePayload(func() {
Attribute("userID", String, "User ID extracted from auth token")
Attribute("requestedAt", Time, "Timestamp when request was received")
})
// Define result fields the interceptor needs to readReadResult(func() {
Attribute("status", Int, "Response status code")
})
// Define result fields the interceptor will writeWriteResult(func() {
Attribute("processedBy", String, "Service instance ID")
Attribute("processedAt", Time, "Timestamp when processed")
Attribute("duration", String, "Processing duration")
})
})
varRetryInterceptor=Interceptor("RetryInterceptor", func() {
Description("Client-side interceptor which retries failed requests")
// No payload or result modifications needed
})
// Interceptors can be used at API, Service, or Method levelvar_=API("ExampleAPI", func() {
// API-wide interceptorServerInterceptor(AuditInterceptor)
})
var_=Service("Example", func() {
// Service-wide interceptor (no-op in this example as already applied at API level)ServerInterceptor(AuditInterceptor)
Method("Add", func() {
// Method-specific interceptorClientInterceptor(RetryInterceptor)
Payload(func() {
// Fields read by audit interceptorAttribute("auth", String, "JWT auth token")
// Fields written by audit interceptorAttribute("userID", String, "User ID extracted from auth token")
Attribute("requestedAt", Time, "Timestamp when request was received")
Required("auth"))
})
Result(func() {
// Fields written by business logic and read by audit interceptorAttribute("status", Int, "Response status code")
// Fields written by audit interceptorAttribute("processedBy", String, "Service instance ID")
Attribute("processedAt", Time, "Timestamp when processed")
Attribute("duration", Duration, "Processing duration")
Required("status")
})
HTTP(func() {
POST("/")
Header("auth:Authorization")
})
})
})
This proposal introduces the following new DSL:
Interceptor(name string, dsl func()) - Defines a new interceptor. Used at design package level to define interceptor types.
ServerInterceptor - Applies a server-side interceptor at API, Service or Method level.
ClientInterceptor - Applies a client-side interceptor at API, Service or Method level.
ReadPayload(dsl func()) - Defines the payload attributes that the interceptor needs to read. Used within Interceptor DSL.
WritePayload(dsl func()) - Defines the payload attributes that the interceptor will modify. Used within Interceptor DSL.
ReadResult(dsl func()) - Defines the result attributes that the interceptor needs to read. Used within Interceptor DSL.
WriteResult(dsl func()) - Defines the result attributes that the interceptor will modify. Used within Interceptor DSL.
Implementation Example
The user code implements the interceptors as follows:
// Server-side audit interceptorfuncAuditInterceptor(ctx context.Context, payload*genservice.AuditPayload, info*genservice.AuditInterceptorInfo,
next goa.NextFunc) (any, error) {
start:=time.Now()
// Get typed access directlypa:=info.Payload()
// Read from payloadauth:=pa.Auth()
userID:=extractUserID(auth) // user provided function// Apply payload modificationspa.SetUserID(userID)
pa.SetRequestedAt(start)
// Continue chainres, err:=next(ctx)
iferr!=nil {
returnnil, err
}
// Get typed result accessra:=info.Result(res)
// Read from resultstatus:=ra.Status()
// Apply result modificationsra.SetProcessedBy(os.Getenv("SERVICE_INSTANCE"))
ra.SetProcessedAt(time.Now())
ra.SetDuration(time.Since(start).String())
// Log audit informationlog.Printf("Request processed: status=%d, duration=%v", status, time.Since(start))
returnres, nil
}
// Server-side registrationpackage main
funcmain() {
// Create servicesvc:=genservice.NewService()
// Create interceptorsinterceptors:=&genservice.ServerInterceptors{
AuditInterceptor: AuditInterceptor,
RetryInterceptor: RetryInterceptor,
}
// Create endpoints with interceptorsendpoints:=genservice.NewEndpoints(svc, interceptors)
// ... rest of server setup
}
The generated interceptor info object provides access to the service and method names as well as the current payload. It also exposes methods to retrieve the payload and result context objects which allow for type-safe access and modification.
// In Goa packagepackage goa
// InterceptorInfo provides context about the current interceptiontypeInterceptorInfostruct {
// Service nameServicestring// Method nameMethodstring// Endpoint contains the current endpoint being executedEndpointEndpoint// Payload contains the method payloadPayloadany
}
// In generated codepackage example
// Type-safe interceptor info with generated methodstypeAuditInfostruct {
*goa.InterceptorInfo// Payload returns a type-safe access for reading and modifying the payloadPayload() AuditPayloadAccess// Result returns a type-safe access for reading and modifying the resultResult(resany) AuditResultAccess
}
There is also a new type defining the signature for the next function that is common to all interceptors:
// In goa packagetypeNextFuncfunc(context.Context) (any, error)
Client-side interceptors are also supported. They follow the same pattern as server-side interceptors. In this example RetryInterceptor does not need to read or modify the payload or result.
Good catch, that's not how interceptors ended up being implemented. Instead there is a single generated interface that exposes all the interceptors. The end user provides the implementation for this interface and Goa calls the right interceptors at the right time. I've added an example: https://github.com/goadesign/examples/tree/features/interceptors/interceptors.
Interceptors in Goa
Overview
This proposal introduces typed interceptors to Goa's design DSL. Interceptors provide a type-safe mechanism for injecting cross-cutting concerns into method execution, with clean interfaces for reading and modifying payloads and results. They can be defined at API, Service, and Method levels.
Requirements
Design
DSL Example
This proposal introduces the following new DSL:
Interceptor(name string, dsl func())
- Defines a new interceptor. Used at design package level to define interceptor types.ServerInterceptor
- Applies a server-side interceptor atAPI
,Service
orMethod
level.ClientInterceptor
- Applies a client-side interceptor atAPI
,Service
orMethod
level.ReadPayload(dsl func())
- Defines the payload attributes that the interceptor needs to read. Used within Interceptor DSL.WritePayload(dsl func())
- Defines the payload attributes that the interceptor will modify. Used within Interceptor DSL.ReadResult(dsl func())
- Defines the result attributes that the interceptor needs to read. Used within Interceptor DSL.WriteResult(dsl func())
- Defines the result attributes that the interceptor will modify. Used within Interceptor DSL.Implementation Example
The user code implements the interceptors as follows:
The generated interceptor info object provides access to the service and method names as well as the current payload. It also exposes methods to retrieve the payload and result context objects which allow for type-safe access and modification.
There is also a new type defining the signature for the next function that is common to all interceptors:
Client-side interceptors are also supported. They follow the same pattern as server-side interceptors. In this example
RetryInterceptor
does not need to read or modify the payload or result.Generated Interceptor Code
A new
interceptors.go
file is generated undergen/<name of service>/interceptors.go
with the following content:Generated Service Code
Additionally the generated
endpoints.go
file is modified to call the interceptors:Generated Client Code
The generated service package
client.go
is updated to hook up the client side interceptors as follows:Key Features
Type-Safe Access
Clean Modification Pattern
Flexible Chain Control
Design Integration
/cc @tchssk @ElectricCookie
The text was updated successfully, but these errors were encountered: