Handling Multiple Views in Streaming Results

Goa’s flexibility allows you to define multiple views for your result types, enabling different representations of data based on the client’s requirements. When dealing with streaming results, managing these views becomes essential to ensure that streamed data is presented appropriately. This section explores how to handle multiple views in streaming results using Goa’s DSL and generated code.

Understanding Views in Goa

Views in Goa allow you to define different representations of your result types. Each view can include a subset of the attributes of the type, tailored to specific use cases or client needs.

Example

var LogEntry = Type("LogEntry", func() {
    Field(1, "timestamp", String, "Time the log entry was created")
    Field(2, "message", String, "The log message")
    Field(3, "level", String, "Log level (INFO, WARN, ERROR, etc)")
    Field(4, "source", String, "Source of the log entry")
    Field(5, "metadata", MapOf(String, String), "Additional metadata")
    Required("timestamp", "message", "level")

    View("default", func() {
        Attribute("timestamp")
        Attribute("message")
        Attribute("level")
    })
    
    View("detailed", func() {
        Attribute("timestamp")
        Attribute("message")
        Attribute("level")
        Attribute("source")
        Attribute("metadata")
    })
})

In this example:

  • default View: Includes basic log information (timestamp, message, and level).
  • detailed View: Includes all log information including source and metadata.

Using SetView in Service Implementations

When streaming results with multiple views, you must specify which view to use when sending each result. This is done using the SetView method generated by Goa, which sets the view context for the streamed data.

Example Design

var _ = Service("logger", func() {
    Method("monitor", func() {
        StreamingPayload(ViewSelector)
        StreamingResult(LogEntry)
        HTTP(func() {
            GET("/logs/monitor")
            Response(StatusOK)
        })
    })
})

Example Implementation

This example assumes a HTTP transport since it makes use of both Payload and StreamingPayload to set the view.

func (s *loggerSvc) Monitor(ctx context.Context, p *logsvc.ViewSelector, stream logsvc.MonitorServerStream) error {
    // Set the view based on the client's request
    stream.SetView(p.View)
    
    // Start monitoring logs
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            logEntry := s.getNextLog()
            if err := stream.Send(logEntry); err != nil {
                return err
            }
        }
    }
}

Client-Side Implementation

func monitorLogsWithView(client logger.Client, view string) error {
    // Initiate the monitor stream with the desired view
    stream, err := client.Monitor(context.Background(), &logger.ViewSelector{ View: view })
    if err != nil {
        return fmt.Errorf("failed to start monitor stream: %w", err)
    }
    defer stream.Close()

    // Receive and process logs
    for {
        logEntry, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return fmt.Errorf("error receiving log: %w", err)
        }

        // Process log entry according to view
        // Default view will only have timestamp, message, and level
        // Detailed view will include source and metadata
        processLogEntry(logEntry)
    }

    return nil
}

Best Practices for Multiple Views in Streaming

  • View Selection: Choose appropriate views based on use cases:

    • Use default view for basic monitoring and alerting
    • Use detailed view for debugging and detailed analysis
  • Performance Considerations:

    • Default view reduces network traffic for routine monitoring
    • Detailed view provides complete information when needed
  • Documentation: Document available views:

// LogEntry views:
// - "default": Basic log information (timestamp, message, level)
// - "detailed": Complete log information including source and metadata

Summary

By applying views to our logger service, we can provide flexible data representations that suit different monitoring needs. The default view offers efficient basic monitoring, while the detailed view supports comprehensive debugging scenarios. This approach optimizes network usage while maintaining the ability to access complete information when needed.