Goa Framework Examples

repository·main·Indexed 42 days ago

https://github.com/goadesign/examples

A collection of microservice examples implemented using the Goa framework. These templates demonstrate specific topics including error handling, file systems, interceptors, retries, custom encodings, and HTTP cookie manipulation using the Goa DSL. The examples cover the use of `goa gen` for boilerplate generation and `goa example` for service scaffolding, with implementations for HTTP and gRPC transports.

Tokens
65.9K
Snippets
239
Records
275
Agent score
52%

What's inside Goa

  1. Explore Goa Security Examples

    main

    The security/ directory contains examples demonstrating how to secure Goa service endpoints using built-in constructs. There are two primary patterns illustrated:

    1. Security Hierarchy: Use the hierarchy/ example to learn how to apply security schemes at different granularities: the API level, the service level, or the individual method level.
    2. Multiple Authentication Schemes: Use the multiauth/ example to learn how to leverage different security schemes (e.g., combining different types of authentication) to secure endpoints.
  2. Implement tus protocol for resumable file uploads

    main

    This example demonstrates how to implement a Goa service compliant with the tus protocol, which allows for resumable file uploads over HTTP.

    Supported HTTP Methods

    The implementation supports the following methods required by the tus core protocol:

    • HEAD: Returns status information on ongoing uploads.
    • PATCH: Used for the actual data upload.
    • OPTIONS: Used for protocol discovery (version, supported extensions).

    It also implements suggested additional methods:

    • POST: Creates the upload target (supports creation and creation-with-upload extensions).
    • DELETE: Cancels uploads (supports termination extension).

    Supported Extensions

    The implementation supports tus protocol version 1.0.0 and includes these extensions:

    • creation
    • creation-with-upload
    • creation-defer-length
    • expiration
    • checksum
    • termination

    Note: The Concatenation extension is not supported.

  3. Organize and chain Server-Side Interceptors

    main

    Server-side interceptors are used to process incoming requests before they reach the service logic. In a Goa service, you can chain multiple interceptors to handle cross-cutting concerns like tracing, auditing, authentication, timeouts, and caching. The order of the chain is critical as it determines the execution flow (e.g., authentication should typically happen before caching or business logic).

    // Example interceptor chain order
    ServerInterceptor(TraceRequest)  // 1. Add trace context
    ServerInterceptor(RequestAudit)  // 2. Timing/logging
    ServerInterceptor(JWTAuth)       // 3. Auth checks
    ServerInterceptor(SetDeadline)   // 4. Timeouts
    ServerInterceptor(Cache)         // 5. Caching
  4. Implement streaming upload and download with Goa

    main

    To implement streaming of binary files without loading entire payloads into memory, use the SkipRequestBodyEncodeDecode and SkipResponseBodyEncodeDecode DSL functions.

    Streaming Uploads

    Use SkipRequestBodyEncodeDecode in the HTTP definition of a method. This instructs Goa to bypass request encoder/decoder generation and instead provide the underlying io.Reader of the HTTP request to your service.

    Important: When using this, any Payload attributes defined must be mapped to HTTP headers or parameters, as the body itself will not be decoded via standard Goa mechanisms.

    Streaming Downloads

    Use SkipResponseBodyEncodeDecode in the HTTP definition. This allows your service to provide an io.Reader that the generated code will stream directly to the HTTP response body. You should typically map result attributes (like file length) to HTTP headers (like Content-Length) using the Response DSL.

    Example design for an upload method:

    Method("upload", func() {
        Payload(func() {
            Attribute("content_type", String, "...")
            Attribute("dir", String, "...")
        })
    
        HTTP(func() {
            POST("/upload/{*dir}")
            Header("content_type:Content-Type")
            SkipRequestBodyEncodeDecode()
        })
    })
  5. How streaming endpoints work in Goa

    main

    In Goa, an endpoint is classified as a streaming endpoint based on the DSLs used in its Method expression. There are three types of streaming behaviors:

    1. Client-to-Server Streaming: Uses the StreamingPayload DSL. The client streams data to the server.
    2. Server-to-Client Streaming: Uses the StreamingResult DSL. The server streams data to the client.
    3. Bidirectional Streaming: Uses both StreamingPayload and StreamingResult. Both client and server can stream data simultaneously.

    The syntax for these streaming DSLs is similar to the standard Payload and Result DSLs.

  6. How idempotent client retries work in Goa

    main

    Goa allows a service to declare that a method is safe to repeat with the same input. When a method is marked as Idempotent() and returns an error marked as Temporary(), the generated HTTP and gRPC clients will automatically retry the call upon encountering that error or transport-level failures (like connection resets).

    Key Rules for Automatic Retries:

    • Requirements: Both the method must be marked Idempotent() AND the error must be marked Temporary().
    • Scope: Retries apply to unary calls with generated, replayable request bodies.
    • Exclusions: Goa does not replay established streams, WebSocket or Server-Sent Events (SSE) connections, multipart requests, or custom raw bodies.

    Contract Projection:

    • HTTP: The OpenAPI specification includes x-goa-idempotent: true.
    • gRPC: The protobuf definition sets idempotency_level = IDEMPOTENT.
    • Clients: Generated clients wrap the endpoint with goa.RetryEndpoint.
    Method("get_message", func() {
        Idempotent()
    
        Error("unavailable", func() {
            Temporary()
        })
    
        // Payload, result, HTTP, and gRPC definitions...
    })
  7. Define and apply security schemes in Goa

    main

    Security schemes are global constructs used to secure entire APIs, specific services, or individual methods. You first define a scheme using one of the security DSL functions, and then apply it using the Security() function at different levels of the hierarchy.

    Available Security Schemes

    • APIKeySecurity: Uses an API key.
    • BasicAuthSecurity: Uses basic authentication.
    • JWTSecurity: Uses JWT tokens.
    • OAuth2Security: Uses OAuth2.

    Security Hierarchy

    Security follows a hierarchical override pattern:

    1. API Level: Applying Security() within an API definition sets the default for all services and methods within that API.
    2. Service Level: Applying Security() within a Service definition overrides the API-level security for all methods in that service.
    3. Method Level: Applying Security() within a Method definition overrides both the Service and API-level security for that specific endpoint.
    var BasicAuth = BasicAuthSecurity("realm", func() {
        Description("Basic auth")
    })
    
    var APIKeyAuth = APIKeySecurity("key", func() {
        Description("API key based authentication")
    })
    
    var JWTAuth = JWTSecurity("jwt", func() {
        Description("JWT based authentication")
    })
    
    var _ = API("secure", func() {
        Security(BasicAuth) // Default for the whole API
        
        Service("svc", func() {
            Security(APIKeyAuth) // Overrides API for this service
            
            Method("method", func() {
                Security(JWTAuth) // Overrides Service for this method
            })
        })
    })
  8. Embed and serve static files in a Goa service

    main

    To serve a web client (HTML/JS/CSS) directly from your Go binary, use Go's embed package in conjunction with the Goa Files directive.

    1. In Goa Design: Use Files("/{*filepath}", "public") to map the URL path to a directory.
    2. In Go Implementation: Use //go:embed public to include the directory in the binary.
    3. In HTTP Setup: The Goa-generated Mount function connects the static file handler to the HTTP mux, allowing files in the public folder to be served at the root path /.
    // In the Go implementation (e.g., monitor.go)
    //go:embed public
    var StaticFS embed.FS
  9. Understand the Concert API type structure and validation

    main

    The API uses a layered type approach to handle different operations with varying requirements:

    • ConcertData: Base type containing all concert fields (no required constraints).
    • ConcertPayload: Used for creation; extends ConcertData and requires all fields.
    • UpdatePayload: Used for updates; extends ConcertData but only requires the concert ID (allows partial updates).
    • Concert: The response type; includes the complete concert details plus a unique ID.

    Validation Rules

    Validation is enforced at the design layer:

    • Artist names: 1-200 characters.
    • Dates: ISO 8601 format (YYYY-MM-DD).
    • Venues: 1-300 characters.
    • Prices: Non-negative integers, maximum $1000 (stored in cents).
    • Pagination: page must be $\ge 1$; limit must be between 1 and 100.
  10. Apply multiple security schemes to an endpoint

    main

    In Goa, you can secure an endpoint using multiple security schemes by passing multiple security variables to the Security function. When multiple schemes are provided, the endpoint requires all of them to be satisfied. You can also use a configuration function within Security to enforce specific scopes within the claims of those security schemes.

    Security(JWTAuth, APIKeyAuth, func() {
        // Enforce presence of both "api:read" and "api:write" scopes in JWT claims.
        Scope("api:read")
        Scope("api:write")
    })
  11. Define errors in Goa DSL

    main

    In your Goa design, you can define errors at the service level or method level.

    1. Default Error Type: If you do not specify a type, Goa uses the default ErrorResult type. This is useful for generic errors like timeouts.
    2. Custom Error Type: You can specify a custom Go struct to be used as the error type. This allows you to return structured data (like a specific error message) in the response body.
    // Use default error type
    Error("timeout") 
    
    // Use custom error type (e.g., DivByZero struct)
    Error("div_by_zero", DivByZero, "division by 0")
    Error("timeout")
    Error("div_by_zero", DivByZero, "division by 0")
  12. Implement custom encodings in Goa

    main

    Goa allows you to provide arbitrary encoders and decoders to your generated code via the generated HTTP server and client factory functions. This is useful when you need to support formats beyond the default JSON, XML, and GOB serializations.

    There are two primary ways to implement custom encodings demonstrated in the examples:

    1. Specifying Content-Type in Design: Use the text example pattern to specify a response content type (e.g., text/plain) directly in your Goa design. This informs the response encoder factory function and can force the default negotiation algorithm to use a specific encoder.

    2. Adding New Encoding Types: Use the cbor example pattern to add entirely new serialization formats. This involves implementing four specific factory functions (two for the server and two for the client) and passing them to the generated factory functions.