Design Overview
The following sections describe how to use the goa DSL to describe services. They provide an overview of the key concepts. Review the GoDocs for a complete reference.
API Expression
The API function is an optional
top-level DSL which lists the global properties of the API such as a name, a
description and a version number. API
may define one or more
Servers potentially exposing
different sets of services. A single service may be exposed by any number (or
no) server. If Server
is omitted then a single server is automatically defined
that exposes all the services defined in the design. The Server
expression is
used when generating command-line clients and OpenAPI specifications.
var _ = API("calc", func() {
Title("Calculator Service")
Description("A service for multiplying numbers, a goa teaser")
// Server describes a single process listening for client requests. The DSL
// defines the set of services that the server hosts as well as hosts details.
Server("calc", func() {
Description("calc hosts the Calculator Service.")
// List the services hosted by this server.
Services("calc")
// List the Hosts and their transport URLs.
Host("development", func() {
Description("Development hosts.")
// Transport specific URLs, supported schemes are:
// 'http', 'https', 'grpc' and 'grpcs' with the respective default
// ports: 80, 443, 8080, 8443.
URI("http://localhost:8000/calc")
URI("grpc://localhost:8080")
})
Host("production", func() {
Description("Production hosts.")
// URIs can be parameterized using {param} notation.
URI("https://{version}.goa.design/calc")
URI("grpcs://{version}.goa.design")
// Variable describes a URI variable.
Variable("version", String, "API version", func() {
// URL parameters must have a default value and/or an
// enum validation.
Default("v1")
})
})
})
})
Service Expression
The Service function defines a group of methods. This maps to a resource in REST or a service declaration in gRPC. A service may define common error responses to all the service methods. See Handling Errors for additional information on defining errors.
var _ = Service("calc", func() {
Description("The calc service performs operations on numbers")
// Method describes a service method (endpoint)
Method("multiply", func() {
// Payload describes the method payload.
// Here the payload is an object that consists of two fields.
Payload(func() {
// Field describes an object field given a field index, a field
// name, a type and a description.
Field(1, "a", Int, "Left operand")
Field(2, "b", Int, "Right operand")
// Required list the names of fields that are required.
Required("a", "b")
})
// Result describes the method result.
// Here the result is a simple integer value.
Result(Int)
// HTTP describes the HTTP transport mapping.
HTTP(func() {
// Requests to the service consist of HTTP GET requests.
// The payload fields are encoded as path parameters.
GET("/multiply/{a}/{b}")
// Responses use a "200 OK" HTTP status.
// The result is encoded in the response body (default).
Response(StatusOK)
})
// GRPC describes the gRPC transport mapping.
GRPC(func() {
// Responses use a "OK" gRPC code.
// The result is encoded in the response message (default).
Response(CodeOK)
})
})
// Serve the file with relative path ./gen/http/openapi.json for
// requests sent to /swagger.json.
Files("/swagger.json", "./gen/http/openapi.json")
})
Method Expression
The service methods are described using Method.
This function defines the method payload (input) and result (output) types. It
may also list an arbitrary number of error return values. An error return value
has a name and optionally a type. Omitting the payload or result type has the
same effect as using the built-in type Empty
which maps to an empty body in
HTTP and to the Empty
message in gRPC. Omitting an error type has the same
effect as using the default error type
ErrorResult.
Method("divide", func() {
Description("Divide returns the integral division of two integers.")
Payload(func() {
Attribute("a", Int, "Left operand")
Attribute("b", Int, "Right operand")
Required("a", "b")
})
Result(Int)
// Error defines an error result.
Error("DivByZero")
HTTP(func() {
GET("/div/{a}/{b}")
// The HTTP status code for responses corresponding to
// the "DivByZero" error is 400 Bad Request.
// The default response for successful requests is
// 200 OK.
Response("DivByZero", StatusBadRequest)
})
GRPC(func() {
// The gRPC code for results corresponding to the
// "DivByZero" error is 3 (INVALID_ARGUMENT).
// The default response for successful requests is
// 0 OK.
Response("DivByZero", CodeInvalidArgument)
})
})
The payload, result and error types define the input and output of the method independently of the transport. In other words the payload and result types should include all the fields that are required by the business logic including fields that are mapped to HTTP headers, URL parameters etc.
gRPC Expression
The gRPC function defines the mapping of the payload and result type attributes to the gRPC message and metadata.
Method("update", func() {
Description("Change account name")
Payload(UpdateAccount)
Result(Empty)
Error("NotFound")
Error("BadRequest")
// gRPC transport
GRPC(func() {
Response("NotFound", CodeNotFound)
Response("BadRequest", CodeInvalidArgument)
})
})
HTTP Expression
The HTTP function defines the
mapping of the payload and result type attributes to the HTTP request path and
query string values as well as the HTTP request and response bodies. The HTTP
function also defines other HTTP-specific properties such as the request path
and the response HTTP status codes.
Method("update", func() {
Description("Change account name")
Payload(UpdateAccount)
Result(Empty)
Error("NotFound")
Error("BadRequest")
// HTTP transport
HTTP(func() {
PUT("/{accountID}") // "accountID" UpdateAccount attribute
Body(func() {
Attribute("name") // "name" UpdateAccount attribute
Required("name")
})
Response(StatusNoContent)
Response("NotFound", StatusNotFound)
Response("BadRequest", StatusBadRequest)
})
})
Method Payload Type
In the example above the accountID
HTTP request path parameter is defined by
the attribute of the UpdateAccount
type with the same name. The HTTP request
body is defined as an object which contains the name
attribute of the
UpdateAccount
payload type.
Any attribute that is not explicitly mapped by the HTTP
function is implicitly
mapped to request body attributes. This makes it simple to define mappings where
only one of the fields for the payload type is mapped to a HTTP header for
example.
The body attributes may also be listed explicitly using the Body
function.
This function accepts either a DSL listing the corresponding payload type
attributes names or a string which corresponds to the name of a single payload
type attribute that defines the shape of the request body as a whole. The latter
makes it possible to use arbitrary types to describe the request body (arrays
and maps for example).
Here is an example of a HTTP mapping that defines the shape of the request body implicitly:
HTTP(func() {
PUT("/{accountID}") // The "accountID" payload type attribute.
Response(StatusNoContent) // All other payload type attributes are
// mapped to the request body.
})
Another example which uses the name of payload type attribute to define the shape of the request body:
HTTP(func() {
PUT("/")
Body("names") // Assumes payload type has attribute "names"
Response(StatusNoContent)
})
Method Result Type
While a service may only define one result type the HTTP
function may list
multiple responses. Each response defines the HTTP status code, response body
shape (if any) and may also list HTTP headers. The Tag
DSL function makes it
possible to define an attribute of the result type that is used to determine
which HTTP response to send. The function specifies the name of a result type
attribute and the value the attribute must have for the response in which the
tag is defined to be used to write the HTTP response.
By default, the shape of the body of responses with HTTP status code 200 is
described by the method result type. The HTTP
function may optionally use
result type attributes to define response headers. Any attribute of the result
type that is not explicitly used to define a response header defines a field of
the response body implicitly. This alleviates the need to repeat all the result
type attributes to define the body since in most cases only a few would map to
headers.
The response body may also be explicitly described using the function Body
.
The function works identically as when used to describe the request body: it may
be given a list of result type attributes in which case the body shape is an
object or the name of a specific attribute in which case the response body shape
is dictated by the type of the attribute.
Assuming the following type definition:
var Account = Type("Account", func() {
Attribute("name", String, "Name of account.")
})
and the following method design:
Method("index", func() {
Description("Index all accounts")
Payload(ListAccounts)
Result(func() {
Attribute("marker", String, "Pagination marker")
Attribute("accounts", CollectionOf(Account), "list of accounts")
})
HTTP(func() {
GET("")
Response(StatusOK, func() {
Header("marker")
Body("accounts")
})
})
})
The HTTP response body for requests sent to the “index” method are of the form
[{"name":"foo"},{"name":"bar"}]
. The same example but with the line defining
the response body removed (Body("accounts")
) produces response bodies of the
form: {"accounts":[{"name":"foo"},{"name":"bar"}]}
since the shape of the
response body is now an object containing all the attributes of the result type
not used to defined headers (only accounts
is left).
Data Types
Goa supports primitive types, array, map, and object types.
The following table lists the supported primitive types and their representation in the generated HTTP and gRPC transport code.
Primitive | HTTP | gRPC |
---|---|---|
Boolean | bool | bool |
Int | int | sint32 |
Int32 | int32 | sint32 |
Int64 | int64 | sint64 |
UInt | uint | uint32 |
UInt32 | uint32 | uint32 |
UInt64 | uint64 | uint64 |
Float32 | float32 | float |
Float64 | float64 | double |
String | string | string |
Bytes | []byte | bytes |
Any | interface{} | * |
* - Not supported |
User-defined types can be defined in Goa using Type or ResultType. A result type is a type that also defines a set of “views”. Each view lists the attributes (fields) that should be rendered when marshaling a result type instance using the view. For example a HTTP API may define a endpoint that lists a collection of entity and another that retrieves specific entities. It may be desirable to limit the number of fields marshaled in the list while keeping all the fields when returning a specific entity. Views make it possible to define a single result type that supports both these scenarios. Note that since views only apply to rendering using a result type in a method payload does not make sense: types can be used both in payloads and results while result types should only be used in results.
Maps can be defined with
MapOf. The syntax is
MapOf(<KeyType>, <ElemType>)
where <KeyType>
can be a primitive, array, or
user type and <ElemType>
can be a primitive, array, user type, or map. Map
types are represented as Go map
in the HTTP transport and protocol buffer
map in the
gRPC transport. Note that the protocol buffer language only supports primitives
(except floats or bytes) as map keys.
Arrays can be defined in two ways:
- ArrayOf which accepts any type and returns a type.
- CollectionOf which only accepts result types and returns a result type.
The result type returned by CollectionOf
contains the same views as the result
type given as argument. Each view simply renders an array where each element
has been projected using the corresponding element view.
Transport-to-Service Type Mapping
The following sections describes how transport-specific requests and responses are mapped to the transport-independent payload and result types.
Payload-to-Request Mapping
The Payload function describes
the shape of the data given as an argument to the service methods. The HTTP
and GRPC
functions define how the payload is built from the incoming request
(server-side) and how the request is built from the payload (client-side).
For HTTP,
- The Param function defines values loaded from path or query string parameters.
- The Header function defines values loaded from HTTP headers.
- The Body function defines values loaded from the request body.
By default, the payload attributes are mapped to HTTP request body. When the payload type is a primitive type, an array or a map the following restrictions apply:
- only
primitive
orarray
types may be used to define path parameters or headers.array
types must use primitive types to define their elements. - only
primitive
,array
, andmap
types may be used to define query string parameters.array
andmap
types must use primitive types to define their elements.
For gRPC,
- The Message function defines values loaded from a gRPC message.
- The Metadata function defines values loaded from a gRPC request metadata.
By default, the payload attributes are mapped to gRPC message. When the payload type is a primitive type, an array or a map the following restrictions apply:
- only
primitive
orarray
types may be used to define gRPC metadata
Result-To-Response Mapping
The Result function describes the
shape of the return data of the service methods. The HTTP
and GRPC
functions
define how the response is built from the result type (server-side) and how the
result is built from the response (client-side).
For HTTP,
- The Header function defines values loaded from HTTP headers.
- The Body function defines values loaded from the response body.
By default, the result attributes are mapped to HTTP response body. When the result type is a primitive type, an array or a map the following restrictions apply:
- only
primitive
orarray
types may be used to define response headers.array
types must use primitive types to define their elements.
For gRPC,
- The Message function defines values loaded into a gRPC message.
- The Headers function defines values loaded into a gRPC header metadata.
- The Trailers function defines values loaded into a gRPC trailer metadata.
By default, the result attributes are mapped to gRPC message. When the result type is a primitive type, an array or a map the following restrictions apply:
- only
primitive
orarray
types may be used to define gRPC header/trailer metadata