Skip to content

Conversation

@FredrikOseberg
Copy link
Contributor

@FredrikOseberg FredrikOseberg commented Aug 27, 2025

Summary

This PR introduces experimental streaming mode support to the
Unleash Go SDK, enabling real-time feature flag updates through
Server-Sent Events (SSE) instead of traditional polling. The
implementation provides instant updates when feature flags change
on the server.

Key Features

  • Real-time updates: SSE-based streaming eliminates polling delay
    for instant feature flag changes
  • Automatic fallback: Seamlessly falls back to polling mode if
    streaming is unavailable
  • Delta processing: Efficient processing of incremental changes
    using hydration and delta events
  • Backward compatibility: Existing polling-based clients continue
    to work without changes
  • Robust error handling: Proper reconnection logic and error
    recovery

Architecture Changes

New Components

  • Streaming Client (streaming_client.go): Handles SSE connections
    and event processing
  • Streaming Processor (streaming_processor.go): Processes delta
    events and manages feature state
  • Delta API (api/delta.go): Defines event types for streaming
    updates including:
    • HydrationEvent: Full state initialization on connection
    • FeatureUpdatedEvent: Individual feature changes
    • FeatureRemovedEvent: Feature deletions
    • SegmentUpdatedEvent: Segment modifications
    • SegmentRemovedEvent: Segment deletions

Key Implementation Details

  • Uses LaunchDarkly's eventsource library for reliable SSE
    connections
  • Implements proper delta processing with hydration for initial
    state
  • Maintains separate streaming and polling code paths for clean
    separation
  • Clean architecture: Streaming client uses only delta
    processing, eliminating redundant full-response logic

Configuration

Enable streaming mode by setting the experimental streaming
configuration:

unleash.Initialize(
unleash.WithUrl("https://your-unleash-instance.com/api/"),
unleash.WithAppName("my-app"),
unleash.WithExperimental(map[string]interface{}{
"type": "streaming",
}),
)

Testing

  • Comprehensive unit tests for streaming components
  • Integration tests with mock SSE server
  • Delta processing tests covering all event types
  • Backward compatibility tests ensuring polling mode continues to
    work

Files Changed

  • Core streaming implementation: 6 new files (~1,200 lines)
  • Delta API support: New event types and parsing logic
  • Configuration updates: Experimental streaming mode support
  • Documentation: Updated README with streaming mode usage
  • Dependencies: Added github.com/launchdarkly/eventsource for SSE
    support

@FredrikOseberg FredrikOseberg moved this from New to In Progress in Issues and PRs Aug 28, 2025
assert.False(t, client.IsEnabled("feature-1"), "feature-1 should be initially disabled")

// Wait for the update event to be processed
time.Sleep(200 * time.Millisecond)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listen to update event

return nil
}

log.Print("Setting up client")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover

config.go Outdated
backupPath string
refreshInterval time.Duration
storage Storage
httpClient *http.Client
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird formatting

repository.go Outdated
if repo.isStreaming {
repo.streamingClient = newStreamingClient(
options,
repo,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is weird that repository created a streaming client and passes itself as argument

// deltaProcessor handles processing of delta events and updating the feature storage
type deltaProcessor struct {
storage Storage
repository *repository // Repository reference for segment manipulation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can it depend on some inverted dependency being the operation that will be called. because now we have a cyclical dependency where repository creates delta processor and processor calls repository creating a cycle.

r.segments = segments

// Update storage
return r.options.storage.Reset(features, true)
Copy link
Contributor

@kwasniew kwasniew Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some claude analysis told me it may lead to race conditions with isEnabled calls but haven't dug deeper

@github-project-automation github-project-automation bot moved this from In Progress to Approved PRs in Issues and PRs Sep 1, 2025
@FredrikOseberg FredrikOseberg merged commit 75017c0 into v5 Sep 1, 2025
5 checks passed
@FredrikOseberg FredrikOseberg deleted the feat/streaming branch September 1, 2025 12:23
@github-project-automation github-project-automation bot moved this from Approved PRs to Done in Issues and PRs Sep 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants