Goa Documentation

repository·v3·Indexed 27 days ago

https://github.com/goadesign/goa

A design-first framework for building APIs and microservices in Go. Goa uses a type-safe DSL to define API intent and automatically generates production-ready server code, client libraries, and OpenAPI/Swagger documentation for protocols including HTTP, gRPC, and JSON-RPC.

Tokens
24.9K
Snippets
61
Records
119
Agent score
91%

What's inside Goa

  1. Overview of Available Goa Agent Skills

    v3

    Goa Agent Skills are reusable packages designed for application repositories using Goa. They facilitate design-first workflows for creating and evolving Goa services.

    Currently available skill:

    • goa-service-designer/: Handles design-first workflows including DSL changes, code generation, HTTP/gRPC mappings, errors, interceptors, and downstream consumers.
  2. Understand the JSON-RPC Code Generation process

    v3

    Goa implements JSON-RPC using a composition over modification approach. Instead of changing HTTP templates, the codegen package manipulates the codegen.File data structure in memory after the base HTTP code is generated.

    The process follows three steps:

    1. Generate Base HTTP Code: Uses httpcodegen.ServerEncodeDecodeFile to create transport-agnostic code.
    2. Modify Sections: Iterates through SectionTemplates to add jsonrpc imports, replace HTTP handler signatures with JSON-RPC signatures, and namespace sections with a jsonrpc- prefix to avoid collisions.
    3. Add JSON-RPC Sections: Appends new sections containing JSON-RPC specific logic (e.g., jsonrpc-server-handler-init).
  3. Install Goa Agent Skills for Cursor

    v3

    To use the goa-service-designer skill in Cursor, copy the skill directory to the .cursor/skills/ directory of your project. Ensure the SKILL.md file is present within the skill directory.

    .cursor/skills/goa-service-designer/SKILL.md
  4. Quick Start: Create and run a Goa service

    v3

    Follow these steps to initialize a new Go module, define a simple service using the Goa DSL, generate the implementation code, and run the server.

    1. Initialize a new Go module.
    2. Create a design directory and define your service in design/design.go using the Goa DSL.
    3. Use goa gen to generate the server and client code.
    4. Use goa example to generate a working implementation.
    5. Run the generated application.
    # Install Goa
    go install goa.design/goa/v3/cmd/goa@latest
    
    # Create a new module
    mkdir hello && cd hello
    go mod init hello
    
    # Define a service in design/design.go
    mkdir design
    cat > design/design.go << EOF
    package design
    
    import . "goa.design/goa/v3/dsl"
    
    var _ = Service("hello", func() {
        Method("say_hello", func() {
            Payload(func() {
                Field(1, "name", String)
                Required("name")
            })
            Result(String)
    
            HTTP(func() {
                GET("/hello/{name}")
            })
        })
    })
    EOF
    
    # Generate the code
    goa gen hello/design
    goa example hello/design
    
    # Build and run
    go mod tidy
    go run cmd/hello/*.go --http-port 8000
    
    # Test the service
    curl http://localhost:8000/hello/world
  5. Configure gRPC mappings in Goa design

    v3

    When designing gRPC interfaces:

    • Metadata: Set package and version metadata for public or versioned APIs.
    • Data: Keep domain data in messages. Use metadata, headers, and trailers only for protocol metadata.
    • Streaming: Use streaming for large or continuous datasets.
    • Stability: Never renumber released Field values.
    • Verification: Check .proto output after changing shared types, streaming methods, or custom protobuf metadata.
  6. Use SSE and WebSocket Modifiers

    v3

    To test specific streaming lifecycle events, use these modifiers in your method names:

    • _final modifier: Sends notifications followed by a final response containing the request ID.
    • _error modifier: Sends notifications followed by a JSON-RPC error.

    Example: _final modifier for SSE

    request:
      params: "ab"  # 2 characters = 2 notifications
      id: "req-1"
    sequence:
      - expect:
          method: "stream_string_final_sse"
          params:
            value: "Stream 1 of 2"
      - expect:
          method: "stream_string_final_sse"
          params:
            value: "Stream 2 of 2"
      - expect:
          id: "req-1"
          result:
            value: "Final response"
    # Example: stream_string_final_sse
    request:
      params: "ab"  # 2 characters = 2 notifications
      id: "req-1"
    sequence:
      - expect:  # Notification (no ID)
          method: "stream_string_final_sse"
          params:
            value: "Stream 1 of 2"
      - expect:  # Notification (no ID)
          method: "stream_string_final_sse"
          params:
            value: "Stream 2 of 2"
      - expect:  # Final response (with ID)
          id: "req-1"
          result:
            value: "Final response"
  7. Understand Data-Driven Action Behaviors

    v3

    The framework generates server behavior based on the method name and the payload data. When writing tests, choose the appropriate action to control how the server responds:

    • echo: Returns the payload exactly as received. For SSE, sends the payload as a notification.
    • transform: Applies predictable transformations:
      • string: Converts to uppercase.
      • array: Reverses the order.
      • object: Uppercases field1, doubles field2, and negates field3.
      • map: Prefixes all keys with transformed_.
    • generate: Ignores the payload and returns fixed values. For SSE, always sends 3 generated notifications.
    • stream (SSE/WebSocket): Behavior is controlled by the payload type:
      • string: Number of messages equals payload length (max 10). Empty/missing payload sends 3 messages.
      • array: Each item generates one notification. Empty array sends one "empty" notification.
      • object: field2 controls the number of notifications (max 10). Default is 3 if field2 is 0 or missing.
      • map: Each key-value pair generates one notification. Empty map sends {"status": "empty"}.
  8. Determine required test coverage for design changes

    v3

    Scale your test coverage based on the risk introduced by the design change:

    • Design-only doc changes: May require no tests.
    • Service behavior changes: Require focused service tests.
    • Transport mapping changes: Require transport-level checks where practical.
    • Error contract changes: Require tests for generated error names and status/code mappings.
    • Streaming or interceptor changes: Require coverage for cleanup, cancellation, and ordering.
  9. Handle presence and collections semantics

    v3

    Avoid using the distinction between nil and empty slices/maps to encode business logic, as they are often indistinguishable after marshaling/unmarshalling.

    • Slices/Arrays: If an empty array is valid, state this in the field description. If presence is critical (e.g., for partial updates), use an explicit field like replace_items, items_present, or a dedicated patch object.
    • Required Collections: Required arrays should not be empty unless the design explicitly permits it.
    • Maps: Do not rely on nil vs empty maps for semantic meaning.
  10. Implement JSON-RPC over WebSocket streaming

    v3

    To build real-time, bidirectional streaming services, implement the HandleStream method in your service implementation. This method serves as the entry point for all WebSocket communication. You use the generated Stream interface to interact with the connection.

    Key capabilities of the Stream interface:

    • Recv(ctx): Receives and dispatches incoming JSON-RPC requests to the appropriate service method.
    • SendMethodName(...): Sends responses or notifications for a specific method.
    • SendError(...): Sends JSON-RPC error responses.
    • Close(): Closes the connection.

    Note that service methods are called automatically when Recv() processes a matching request, but you can also call them manually from HandleStream for server-initiated communication.

    func (s *serviceImpl) HandleStream(ctx context.Context, stream ServiceName.Stream) error {
        // User implements their streaming strategy here.
        // Can listen to channels, timers, events, etc.
        // Can call stream.Recv() to process incoming JSON-RPC requests.
        // Can call stream.SendMethodName() to send responses or notifications.
    }
  11. Apply boundary validation to fields

    v3

    Use Goa's design DSL to enforce data constraints at the boundary. Do not duplicate this validation logic in your service implementation code.

    Available Validation Tools:

    • Presence: Use Required to mark mandatory fields.
    • Formats: Use built-in formats like FormatUUID, FormatEmail, FormatDateTime, and FormatURI.
    • Constraints: Use Enum, Pattern, Minimum, Maximum, MinLength, and MaxLength.
    • Defaults: Use Default to provide fallback values.
    Payload(func() {
        Field(1, "accountId", AccountID, "Account identifier.")
        Field(2, "limit", Int, "Maximum number of items.", func() {
            Default(50)
            Minimum(1)
            Maximum(100)
        })
        Required("accountId")
    })
  12. Configure HTTP mappings in Goa design

    v3

    When designing HTTP interfaces, follow these best practices for resource mapping and parameter handling:

    • Resource Naming: Use resource nouns, plural names, and a stable hierarchy. Use HTTP methods for actions.
    • Pathing: Put stable prefixes in API-level or service-level HTTP(Path(...)). Use Parent and CanonicalMethod only when nested resources improve clarity.
    • Parameters: Map path, query, header, and body fields using Param, Header, and Body. Use explicit wire-name mapping if attribute names differ from HTTP element names.
    • Status Codes: Choose codes intentionally (e.g., StatusCreated for successful creations).
    • Payload Checks:
      • Every path token must map to a payload field.
      • Use Body("field") only when the request body should be the raw value of that specific field.
      • Unmapped object attributes are automatically encoded in the body.
    • CORS: Use the CORS plugin for browser-facing cross-origin policies.
    • Static Content: Use Files only for HTTP static content.
    HTTP(func() {
        GET("/accounts/{account_id}/items")
        Param("limit")
        Response(StatusOK)
        Response("not_found", StatusNotFound)
    })