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
, andlevel
). - detailed View: Includes all log information including
source
andmetadata
.
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
- Use
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.