Skip to content

Default the replicator health URL to http://localhost:8090/ready like the other SDKs - #203

Merged
bpapillon merged 1 commit into
mainfrom
ryan/replicator-health-url-default
Sep 30, 2026
Merged

bpapillon merged 1 commit into
mainfrom
ryan/replicator-health-url-default

Conversation

@ryanechternacht

Copy link
Copy Markdown
Member

Problem

The C# SDK had no default health URL for replicator mode, unlike the other Schematic SDKs.

  • ClientOptions.ReplicatorHealthUrl was nullable with no default.
  • WithReplicatorMode(string healthUrl) required a URL and threw on an empty one.
  • Setting ReplicatorMode = true without a URL made the Schematic constructor throw ArgumentException("ReplicatorHealthUrl is required when ReplicatorMode is enabled"). When DatastreamClientAdapter was constructed directly in replicator mode without a URL, it silently created no ReplicatorHealthService, so the replicator was never reported ready and no cache version was available.

What the other SDKs do

Go (core/custom_options.go, WithDatastream) applies defaults after all options are processed, only when replicator mode is on:

if dataStreamOptions.ReplicatorMode {
    if dataStreamOptions.ReplicatorHealthURL == "" {
        dataStreamOptions.ReplicatorHealthURL = "http://localhost:8090/ready"
    }
    if dataStreamOptions.ReplicatorHealthCheck == 0 {
        dataStreamOptions.ReplicatorHealthCheck = 30 * time.Second
    }
}

Python (replicator_health_url default in datastream_client.py), Java (DatastreamOptions.DEFAULT_REPLICATOR_HEALTH_URL) and Ruby (REPLICATOR_HEALTH_URL) use the same http://localhost:8090/ready. An explicitly configured URL always wins.

The health check interval already matches: C#'s ReplicatorHealthService defaults to 30 seconds, the same as Go. The interval is not configurable from ClientOptions in C#, so there was nothing to change there.

Change

  • Add ClientOptions.DefaultReplicatorHealthUrl = "http://localhost:8090/ready".
  • Resolve the effective URL in one place (ClientOptions.ResolveReplicatorHealthUrl, internal). In replicator mode a null, empty or whitespace URL resolves to the default. Outside replicator mode it resolves to null, so no health service is created.
  • The Schematic constructor passes the resolved URL to DatastreamClientAdapter and no longer throws when ReplicatorMode = true has no URL. DatastreamClientAdapter applies the same resolution, so direct construction behaves the same way.
  • Add a parameterless WithReplicatorMode() overload that uses the default.

Compatibility

I added an overload instead of making healthUrl optional. Both are source compatible, but an overload leaves the existing WithReplicatorMode(ClientOptions, string) signature and its contract exactly as they were. Already-compiled callers bind to the same method, and the default URL isn't baked into their call sites the way an optional parameter's default value would be. It also keeps WithReplicatorMode(null) from reading as "use the default".

WithReplicatorMode(string) still throws ArgumentException for an explicitly passed null, empty or whitespace string. Passing a blank value explicitly usually means a config value that didn't load (for example, an unset environment variable). Failing fast was already the documented behavior, and silently pointing that caller at localhost could hide the misconfiguration. The exception message now points to the parameterless overload. This is a small, documented divergence from Go, where WithReplicatorHealthURL("") falls back to the default. The property path does match Go: a blank ReplicatorHealthUrl is treated as unset.

The only behavior change for existing code: ReplicatorMode = true without a URL now uses the default instead of throwing in the Schematic constructor.

Docs

  • README: the replicator mode example uses .WithReplicatorMode(), a paragraph states the default, the 30 second poll interval, precedence and the blank-string behavior, and the options table lists .WithReplicatorMode() and ReplicatorHealthUrl with the default.
  • XML doc comments on ReplicatorHealthUrl, DefaultReplicatorHealthUrl, both WithReplicatorMode overloads and the DatastreamClientAdapter constructor state the default.

Tests

New src/SchematicHQ.Client.Test/Datastream/ReplicatorHealthUrlDefaultTests.cs (16 tests):

  • Replicator mode with no URL uses http://localhost:8090/ready through the ReplicatorMode property (null, empty and whitespace), through WithReplicatorMode(), and end to end through the Schematic constructor for both paths.
  • An explicit URL overrides the default through the property, WithReplicatorMode(url), the adapter, and the Schematic constructor.
  • Non-replicator mode resolves no URL and the adapter creates no health service, even when ReplicatorHealthUrl is set.
  • WithReplicatorMode("") and WithReplicatorMode(" ") still throw.

The Schematic constructor tests use a Redis config with abortConnect=false, so they don't need a running Redis.

Locally, with .NET SDK 9.0.109: the Release library build succeeds for all targets and all 454 tests pass.

🤖 Generated with Claude Code

Replicator mode now falls back to http://localhost:8090/ready when no
health URL is configured, matching the Go, Python, Java and Ruby SDKs.
The default applies to both ReplicatorMode = true and the
WithReplicatorMode helper, and an explicit URL still wins.

- Add ClientOptions.DefaultReplicatorHealthUrl and resolve the URL in
  the Schematic constructor and DatastreamClientAdapter.
- Remove the constructor exception for ReplicatorMode without a URL.
- Add a parameterless WithReplicatorMode() overload. The existing
  WithReplicatorMode(string) overload is unchanged and still rejects an
  explicitly passed blank URL.
- Document the default in the README and XML docs.
- Add tests for the default, explicit overrides and non-replicator mode.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@bpapillon
bpapillon merged commit 8d594a2 into main Sep 30, 2026
4 checks passed
@bpapillon
bpapillon deleted the ryan/replicator-health-url-default branch September 30, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants