The first component that Goa generates is the service interface layer. This foundational layer defines both the API contract and the service implementation interface. It includes all the method signatures that implement your API endpoints, complete with payload and result type definitions that specify the data structures used in your service operations.
For example, assuming the following design:
var _ = Service("calc", func() {
Description("The calc service provides operations to add and multiply numbers.")
Method("add", func() {
Description("Add returns the sum of a and b")
Payload(func() {
Attribute("a", Int)
Attribute("b", Int)
})
Result(Int)
})
Method("multiply", func() {
Description("Multiply returns the product of a and b")
Payload(func() {
Attribute("a", Int)
Attribute("b", Int)
})
Result(Int)
})
})
Based on this design, Goa generates a service interface in
gen/calc/service.go
that looks like:
// The calc service provides operations to add and multiply numbers.
type Service interface {
// Add returns the sum of a and b
Add(context.Context, *AddPayload) (res int, err error)
// Multiply returns the product of a and b
Multiply(context.Context, *MultiplyPayload) (res int, err error)
}
// AddPayload is the payload type of the calc service add method.
type AddPayload struct {
A int32
B int32
}
// MultiplyPayload is the payload type of the calc service multiply method.
type MultiplyPayload struct {
A int32
B int32
}
Goa also generates constants that can be referred to when configuring the service and the observability stack, such as the service name and method names:
// APIName is the name of the API as defined in the design.
const APIName = "calc"
// APIVersion is the version of the API as defined in the design.
const APIVersion = "0.0.1"
// ServiceName is the name of the service as defined in the design. This is the
// same value that is set in the endpoint request contexts under the ServiceKey
// key.
const ServiceName = "calc"
// MethodNames lists the service method names as defined in the design. These
// are the same values that are set in the endpoint request contexts under the
// MethodKey key.
var MethodNames = [1]string{"multiply"}
Next, Goa generates the endpoint layer in gen/calc/endpoints.go
, this layer
exposes the service methods in a transport agnostic way. This makes it possible
to apply middleware and other cross-cutting concerns to the service methods:
// Endpoints wraps the "calc" service endpoints.
type Endpoints struct {
Add goa.Endpoint
Multiply goa.Endpoint
}
// NewEndpoints wraps the methods of the "calc" service with endpoints.
func NewEndpoints(s Service) *Endpoints {
return &Endpoints{
Add: NewAddEndpoint(s),
Multiply: NewMultiplyEndpoint(s),
}
}
The Endpoints
struct can be initialized with the service implementation and
used to create the transport-specific server and client implementations.
Goa also generates individual endpoint implementations that wrap the service methods:
// NewAddEndpoint returns an endpoint that invokes the "calc" service "add"
// method.
func NewAddEndpoint(s Service) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
p := req.(*AddPayload)
return s.Add(ctx, p)
}
}
This pattern allows you to apply middleware to specific endpoints or replace them entirely with custom implementations.
Finally Goa generates a Use
function that can be used to apply middleware to
all the service methods:
// Use applies the given middleware to all the "calc" service endpoints.
func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) {
e.Add = m(e.Add)
e.Multiply = m(e.Multiply)
}
And enpoint middleware is a function that takes an endpoint and returns a new
endpoint. The implementation can alter the request payload and result, modify the
context, check for errors, and perform any operation that needs to be done before
or after the endpoint is invoked. A Goa endpoint is defined in the goa
package
as:
// Endpoint exposes service methods to remote clients independently of the
// underlying transport.
type Endpoint func(ctx context.Context, req any) (res any, err error)
For example the following Goa endpoint middleware logs the request and response:
func LoggingMiddleware(next goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req any) (res any, err error) {
log.Printf("request: %v", req)
res, err = next(ctx, req)
log.Printf("response: %v", res)
return
}
}
You can apply this middleware to the service endpoints using:
endpoints.Use(LoggingMiddleware)