Fluens.Web.Discovery
0.7.10
dotnet add package Fluens.Web.Discovery --version 0.7.10
NuGet\Install-Package Fluens.Web.Discovery -Version 0.7.10
<PackageReference Include="Fluens.Web.Discovery" Version="0.7.10" />
<PackageVersion Include="Fluens.Web.Discovery" Version="0.7.10" />
<PackageReference Include="Fluens.Web.Discovery" />
paket add Fluens.Web.Discovery --version 0.7.10
#r "nuget: Fluens.Web.Discovery, 0.7.10"
#:package Fluens.Web.Discovery@0.7.10
#addin nuget:?package=Fluens.Web.Discovery&version=0.7.10
#tool nuget:?package=Fluens.Web.Discovery&version=0.7.10
Fluens.Web.Discovery
Consul self-registration and a central, blocking-query-refreshed service catalog for Fluens web applications that start on dynamic ports.
The library owns both halves of discovery per ADR 20260529000001:
- Registration (write) — the application registers itself into Consul after Kestrel binds, using its real post-start address and port, and deregisters on shutdown. A self-healing reconciler re-applies the held registration on an interval (idempotent upsert), surviving Consul auto-deregistration after transient unhealth.
- Resolution (read) — a single long-polling worker keeps an in-memory
IServiceCatalog(the contract lives inFluens.Web) converged with Consul; the mesh and HTTP clients read the catalog snapshot instead of each issuing their own Consul query. The worker watches both registration topology and health-state transitions, so a peer recovering fromcriticalis picked up within seconds without a topology change.
The library owns the fluens- meta/tag namespace (DiscoveryMeta: fluens-version, fluens-cluster). The
hyphen separator is DNS-safe and legal in both Consul meta keys (which reject .) and tags.
Install
dotnet add package Fluens.Web.Discovery
Usage
builder.AddFluens()
.AddWeb()
.AddDiscovery(); // reads "Fluens:Web:Discovery"; no-op when Enabled == false
Both AddDiscovery() and the idempotent TryAddDiscovery() accept an optional
Action<DiscoveryOptions>? callback that is honoured and validated even with no matching
Fluens:Web:Discovery configuration section present.
Configuration (appsettings.json):
{
"Fluens": {
"Web": {
"Discovery": {
"Enabled": true,
"Url": "http://localhost:8500",
"AdvertisedHost": null,
"ClusterId": "cluster-1",
"Tags": [ "grpc" ],
"Meta": { "region": "eu-west" },
"ReregisterIntervalSeconds": 30,
"HealthCheckPath": "/health",
"HealthCheckIntervalSeconds": 10,
"DeregisterCriticalServiceAfterSeconds": 60,
"CatalogWaitSeconds": 300
}
}
}
}
Options
| Option | Default | Meaning |
|---|---|---|
Enabled |
true |
Master flag; false makes AddDiscovery() a no-op. |
Url |
— | Consul agent base URL. Required when enabled. |
AdvertisedHost |
null |
Overrides the bound host (wildcard/Docker/NAT/cross-host). When unset the bound address is trusted. |
ClusterId |
null |
When set, published as the cluster service meta. |
Tags |
[] |
Extra tags merged with auto tags. |
Meta |
{} |
Extra meta merged with auto version/cluster (user wins on collision). |
ReregisterIntervalSeconds |
30 |
Interval of the idempotent-upsert reconciler. |
HealthCheckPath |
/health |
HTTP check path used when a health source is configured. |
HealthCheckIntervalSeconds |
10 |
Consul check poll interval. |
DeregisterCriticalServiceAfterSeconds |
60 |
Consul DeregisterCriticalServiceAfter auto-cleanup. |
CatalogWaitSeconds |
300 |
Max wait time (seconds) of each catalog blocking query (>0). |
Address normalization
The advertised address is computed once from IServerAddressesFeature and held. Wildcard/loopback bind
tokens ([::], [::1], *, 0.0.0.0) are normalized to localhost. Set AdvertisedHost to advertise a
routable host instead of the bound one.
Health check
A health check is registered only when a source is configured, with precedence:
- an explicit health path passed to the registration call,
- else
IHealthEndpointInforesolved from DI (e.g. provided byFluens.Web.Healthchecks), - else none.
When present it is a Consul HTTP check with DeregisterCriticalServiceAfter as the native auto-cleanup.
Resolution
AddDiscovery() registers a singleton IServiceCatalog (ServiceCatalog) and a CatalogRefreshWorker.
The worker runs two concurrent Consul blocking queries — Catalog.Services (registration topology) and
Health.State(HealthStatus.Any) (health transitions), each on its own WaitIndex and bounded by
CatalogWaitSeconds — and rebuilds the snapshot as soon as either advances. It then reads the current
topology plus per-service Health.Service(passingOnly), assembles an immutable snapshot, and swaps it into
the catalog. Reacting to the health index means a peer's critical→passing transition wakes a refresh within
seconds even when no service was added or removed. The query that loses the race is cancelled and awaited
before the pass returns, so it releases its connection immediately instead of lingering abandoned until the
next pass re-arms it — the cancelled query simply re-issues with its unchanged WaitIndex next time, so no
change is lost. Consumers read the catalog directly:
// Resolve a single healthy endpoint (round-robins across every instance of the name, ignoring tags/meta).
ResolvedServiceEndpoint? endpoint = catalog.Resolve("orders.eu");
// Enumerate services by tag (and optional meta), filtered at instance granularity: an instance
// contributes only when it, alone, carries the tag and matches every metaMatch pair, so two instances
// of one service with different "fluens-cluster" meta each resolve under their own filter.
IReadOnlyList<DiscoveredService> matches = catalog.List(
"grpc",
new Dictionary<string, string> { ["fluens-cluster"] = "cluster-1" });
// React to topology changes.
IChangeToken token = catalog.GetChangeToken();
Sibling libraries that require the catalog (for example the messaging mesh) call the idempotent
TryAddDiscovery() — it wires discovery exactly once, so the catalog worker is never registered twice.
ADR 20260529000004 — endpoint resolution only. The catalog answers where to deliver now; it is not the source of mesh peer subscriptions, and
List("fluens-mesh", …)is not how the mesh finds its peers. Peer identity comes from a durable Consul-KV subscription store (fluens/<ClusterId>/subscriptions/<app>, read over the sameIConsulClientthis library registers); the mesh resolves each owed peer's live address with a plaincatalog.Resolve(app), so a peer going health-critical drops from the catalog without dropping from the mesh's owed-app set. Because that KV store is not re-derivable from a fresh Consul, run Consul persistently (-server -bootstrap-expect=1 -data-dir+ a named volume), never-dev, wherever mesh durability is load-bearing.
Contributor model
A sibling library can advertise additive tags/meta into this application's registration without Discovery
depending on it, by registering an IServiceRegistrationContributor:
internal sealed class MeshContributor : IServiceRegistrationContributor
{
public ServiceRegistrationContribution Contribute() =>
new(["fluens-mesh"], new Dictionary<string, string>(StringComparer.Ordinal));
}
services.AddSingleton<IServiceRegistrationContributor, MeshContributor>();
Contributions merge into the descriptor with auto → contributor → user precedence; tags are unioned with
ordinal de-duplication. The mesh uses this to advertise the fluens-mesh tag on its own registration; the
tag identifies mesh-capable services in the catalog, but peer discovery itself goes through the Consul-KV
subscription store + Resolve, not a List("fluens-mesh", …) call — see the ADR note above.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Consul (>= 1.8.0)
- Fluens.Web (>= 0.7.10)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Fluens.Web.Discovery:
| Package | Downloads |
|---|---|
|
Fluens.Web.Messaging.Dashboard
Cluster-wide monitoring surface for the Fluens cross-application messaging mesh: dead-letter queries, inbox lag, and stats snapshots. |
|
|
Fluens.Web.Messaging
P2P HTTP/Protobuf mesh adapter for cross-application Fluens messaging, with shared-key signed cluster authentication. |
GitHub repositories
This package is not used by any popular GitHub repositories.