GuicedEE Service Registry
Named service registry with health monitoring — register, discover, and resolve services by simple name.
Core Concept
The service registry maintains a map of name → URL + health status. Services are registered via annotations, auto-constructed from cloud DNS suffix, or provided by SPI plugins. Periodic health checks keep status current. The rest-client module can resolve registry:service-name URLs or bare service names at call time.
Required Flow
- Add
com.guicedee:service-registrydependency. - Add
requires com.guicedee.service.registry;tomodule-info.java. - Declare services on
package-info.java:@ServiceRegistryOptions(healthCheckInterval = 30) @RegisteredService(name = "jwebmp-website", aliases = {"jwebmp", "jwebswing"}, externalUrls = {"https://jwebmp.com", "https://jwebswing.com"}, kubernetesUrl = "http://jwebmp-website.default.svc.cluster.local") @RegisteredService(name = "payment-api", url = "${PAYMENT_URL}", healthPath = "/status") @RegisteredService(name = "hello-service", url = "http://hello-service.myns.svc.cluster.local", healthPath = "/healthz") package com.myapp; - Use services by name:
String url = ServiceRegistry.url("jwebmp-website"); String url = ServiceRegistry.url("jwebmp"); // alias resolution String ext = ServiceRegistry.externalUrl("jwebmp-website"); // → https://jwebmp.com List<String> exts = ServiceRegistry.externalUrls("jwebmp-website"); String k8s = ServiceRegistry.kubernetesUrl("jwebmp-website"); Optional<String> healthy = ServiceRegistry.healthyUrl("jwebmp-website");
JPMS Module Name
com.guicedee.service.registry
Lifecycle Hooks
| Hook | Sort Order | What It Does |
|---|---|---|
| ServiceRegistryPreStartup | MIN+150 | Scans annotations, constructs URLs, self-registers, runs SPI providers |
| ServiceRegistryPostStartup | MIN+700 | Starts Vert.x periodic health check timer |
Key Classes
| Class | Package | Role |
|---|---|---|
| ServiceRegistry | com.guicedee.service.registry | Central static registry — get(), url(), externalUrl(), externalUrls(), kubernetesUrl(), healthyUrl(), resolve(), register(), registerAlias() |
| ServiceEntry | com.guicedee.service.registry | Record: name, url, externalUrls, kubernetesUrl, healthPath, status, lastChecked, metadata |
| ServiceStatus | com.guicedee.service.registry | Enum: UP, DOWN, UNKNOWN, DEGRADED |
| ServiceRegistryOptions | com.guicedee.service.registry | Annotation: healthCheckInterval, healthPath, registerSelf, useHttps, defaultPort |
| RegisteredService | com.guicedee.service.registry | Annotation: declares a known service (repeatable) — name, url, healthPath, aliases, externalUrls, externalUrl, kubernetesUrl |
| IServiceRegistryProvider | com.guicedee.service.registry | SPI for external catalogs (Azure API, Consul, static config) |
URL Resolution Order
- If
@RegisteredService(url = "...")is provided → use it (supports${ENV_VAR}placeholders) - If url is empty, auto-construct from DNS suffix:
- Check
CONTAINER_APP_ENV_DNS_SUFFIXenv var (Azure Container Apps) - Check
runtime-autoconfigureextras.dnsSuffix (if on classpath) - Check
SERVICE_REGISTRY_DNS_SUFFIXenv var (manual fallback) - URL =
https://{name}.{dnsSuffix}
- Check
- If no DNS suffix available → warning logged, service skipped
Aliases & External URLs
aliases = {"jwebmp", "jwebswing"}— all resolve to the same service entryexternalUrls = {"https://jwebmp.com", "https://jwebswing.com"}— public custom domainsexternalUrl = "https://guicedee.com"— single external URL shorthandkubernetesUrl = "http://svc.ns.svc.cluster.local"— K8s internal cluster URLServiceRegistry.externalUrl("name")returns first external URL or falls back to internalServiceRegistry.kubernetesUrl("name")returns K8s URL or falls back to internal
Per-Service Health Paths
Each @RegisteredService can specify its own healthPath:
@RegisteredService(name = "legacy-app", healthPath = "/status")
@RegisteredService(name = "k8s-service", healthPath = "/healthz")
@RegisteredService(name = "standard-service") // uses registry default "/health/ready"
ServiceEntry.healthUrl() concatenates url + healthPath to get the full health check URL.
Self-Registration
When registerSelf = true (default) and runtime-autoconfigure is on classpath:
- Uses
RuntimeAutoConfigurePreStartup.current()to get serviceName, hostname, port - Registers self with status UP and metadata
{self: "true"} - Self entries are skipped during health checks
Automatic Discovery (preferred — do not hand-roll)
GuicedEE discovers peers automatically; do not maintain a hand-written peer
list or build a custom IServiceRegistryProvider just to enumerate known services.
Three built-in mechanisms cooperate:
- Self-registration (
registerSelf = true+runtime-autoconfigure) — every service advertises its own identity/host/port at startup. No peer declares another. - DNS-suffix URL construction — when a name has no explicit URL, the registry
builds
https://{name}.{dnsSuffix}from the first available ofCONTAINER_APP_ENV_DNS_SUFFIX(Azure Container Apps),runtime-autoconfigurednsSuffix, orSERVICE_REGISTRY_DNS_SUFFIX(manual fallback). service-discoverymodule (com.guicedee.vertx.servicediscovery) — a Vert.x service resolver (Kubernetes / SRV aware) that resolves service names to live addresses at call time. Add the dependency +requires com.guicedee.vertx.servicediscovery; it self-activates via lifecycle SPIs (no wiring). This is the "automatic discovery" backbone for clustered/K8s deployments.
// Idiomatic: declare ONLY options; rely on self-registration + discovery for peers.
@ServiceRegistryOptions(healthCheckInterval = 30, registerSelf = true, useHttps = true)
package com.myapp;
When to still use
@RegisteredService. Only for genuinely external / third-party endpoints that cannot self-register (a legacy API, a public site). Never to list your own platform's GuicedEE services — those self-register and are discovered.
Local-dev caveat. With no cloud platform and no DNS suffix,
runtime-autoconfigurecannot resolve a public URL, so the self entry may carry an empty URL and a periodic health check can show itDOWN. This is expected for plain local runs — real discovery happens in the target environment. SetSERVICE_REGISTRY_DNS_SUFFIXlocally if you want name → URL construction, or run the cluster (K8s/compose) where the resolver applies.
Health Checks
- Periodic HTTP GET to
{url}{healthPath}using Vert.x WebClient - 2xx/3xx → UP, 503 → DEGRADED, other → DOWN, connection failure → DOWN
- Configurable interval (default 30s) and timeout (default 5000ms)
trustAll = trueby default (for dev clusters)
Integration with rest-client
The rest-client auto-resolves from service registry (no hard dependency):
// Explicit registry: prefix
@Endpoint(url = "registry:jwebmp-website")
@Named("jwebmp") private RestClient<Void, Response> client;
// Bare service name (auto-detected if registered)
@Endpoint(url = "jwebmp-website")
@Named("jwebmp") private RestClient<Void, Response> client;
// Full URL (passes through unchanged)
@Endpoint(url = "https://api.example.com/v1")
@Named("example") private RestClient<Void, Response> client;
Optional Dependencies
| Module | Maven artifact / module name | What It Adds |
|---|---|---|
| runtime-autoconfigure | com.guicedee:runtime-autoconfigure (com.guicedee.runtime.autoconfigure) | Self-registration + DNS suffix auto-detection across cloud platforms (AWS, Azure, GCP, K8s, Fly.io, Render, …) |
| service-discovery | com.guicedee:service-discovery (com.guicedee.vertx.servicediscovery) | Automatic Vert.x service resolution (Kubernetes / SRV aware) via the IServiceResolverProvider SPI; self-activates through lifecycle hooks |
@ServiceRegistryOptions Attributes
| Property | Default | Purpose |
|---|---|---|
| healthCheckInterval | 30 | Seconds between checks (0 = disabled) |
| healthPath | "/health/ready" | Default health endpoint path |
| registerSelf | true | Auto-register using runtime-autoconfigure |
| healthCheckTimeout | 5000 | HTTP timeout in ms |
| useHttps | true | HTTPS for auto-constructed URLs |
| defaultPort | 0 | Port for auto-constructed URLs (0 = omit) |
@RegisteredService Attributes
| Property | Default | Purpose |
|---|---|---|
| name | (required) | Simple logical service name |
| url | "" | Full URL (empty = auto-construct from DNS suffix). Supports ${ENV_VAR} |
| healthPath | "" | Health path override (empty = use registry default) |
| aliases | {} | Alternative names that resolve to this service |
| externalUrls | {} | External/public URLs (custom domains). Supports ${ENV_VAR} |
| externalUrl | "" | Single external URL shorthand. Supports ${ENV_VAR} |
| kubernetesUrl | "" | Kubernetes internal cluster URL. Supports ${ENV_VAR} |
IServiceRegistryProvider SPI
public class AzureCatalogProvider implements IServiceRegistryProvider<AzureCatalogProvider> {
@Override
public List<ServiceEntry> discover() {
// Call Azure API, return container apps in environment
}
@Override
public String providerId() { return "azure-catalog"; }
}
Register in module-info.java:
provides IServiceRegistryProvider with AzureCatalogProvider;
Don't reach for this SPI to list your own services. It exists to bridge a real external catalog (Azure Resource Graph, Consul, a CMDB). Implementing one that just reads a static/env-driven peer list (e.g.
NE1_REGISTRY_PEERS=name=url;…) is an anti-pattern — it re-invents what self-registration + theservice-discoveryresolver + DNS-suffix construction already do automatically. Prefer those (see Automatic Discovery); use a custom provider only when integrating an authoritative external registry.
Non-Negotiable Constraints
- Module name:
com.guicedee.service.registry IGuicePreStartup.onStartup()returnsList<Future<Boolean>>(Vert.x Future)IGuicePostStartup.postLoad()returnsList<Uni<Boolean>>(Mutiny Uni)- Vert.x WebClient module name is
io.vertx.web.client - Vert.x Web module name is
io.vertx.web - Sort order must be after
RuntimeAutoConfigurePreStartup(MIN+50) and afterVertXPreStartup - All services stored lowercase/trimmed
resolveEnvPlaceholdersis public for cross-package access