Concept
When a Silky microservice host starts, one of its most critical tasks is publishing its service metadata to the registry — so that other microservices in the cluster can discover it and establish RPC connections.
Silky models a service host as a Server object (also called a "service provider"):
| Property | Description |
|---|---|
HostName | Equal to the startup assembly name |
Endpoints | All endpoints (RPC, HTTP, WebSocket) this host exposes |
Services | All service descriptors this host implements |
DefaultServerProvider
DefaultServerProvider (implements IServerProvider) assembles the Server descriptor:
public class DefaultServerProvider : IServerProvider
{
private readonly IServer _server;
private readonly IServiceManager _serviceManager;
public DefaultServerProvider(IServiceManager serviceManager, ISerializer serializer)
{
_serviceManager = serviceManager;
_server = new Server(EngineContext.Current.HostName);
}
// Called when the host supports DotNetty RPC
public void AddRpcServices()
{
var rpcEndpoint = EndpointHelper.GetLocalRpcEndpoint(); // local IP + RPC port
_server.Endpoints.Add(rpcEndpoint);
var rpcServices = _serviceManager.GetLocalService(ServiceProtocol.Rpc);
foreach (var rpcService in rpcServices)
{
_server.Services.Add(rpcService.ServiceDescriptor);
}
}
// Called when the host exposes HTTP endpoints
public void AddHttpServices()
{
var webEndpoint = RpcEndpointHelper.GetLocalWebEndpoint();
_server.Endpoints.Add(webEndpoint);
}
// Called when the host exposes WebSocket endpoints
public void AddWsServices()
{
var wsEndpoint = RpcEndpointHelper.GetWsEndpoint();
_server.Endpoints.Add(wsEndpoint);
var wsServices = _serviceManager.GetLocalService(ServiceProtocol.Ws);
foreach (var wsService in wsServices)
{
_server.Services.Add(wsService.ServiceDescriptor);
}
}
}
Registration to the Registry
After DefaultServerProvider.GetServer() produces the complete ServerDescriptor, it is written to the registry (Zookeeper / Nacos / Consul) during module Initialize().
Distributed Lock Protection
To prevent multiple instances starting simultaneously and racing to write the same service route data, a distributed lock is acquired before writing:
// Pseudo-code of the registration process
using (await _distributedLock.AcquireAsync(lockKey, timeout))
{
var existingDescriptor = await _registryClient.GetServerDescriptor(hostName);
var merged = MergeOrReplace(existingDescriptor, newDescriptor);
await _registryClient.SetServerDescriptor(merged);
}
Service Route Record Structure
The service route record written to the registry contains:
{
"HostName": "OrderService",
"Endpoints": [
{ "Host": "192.168.1.10", "Port": 2200, "ServiceProtocol": "Rpc" }
],
"Services": [
{
"Id": "IOrderAppService",
"ServiceEntries": [
{ "Id": "IOrderAppService.GetAsync.id.GET", "Router": "GET /api/order/{id}" }
]
}
]
}
Route Synchronization
After initial registration, route information stays synchronized with the registry through:
- Heartbeat: Periodic writes to the registry to refresh TTL (prevents stale entries)
- Subscription: Each node subscribes to registry change events and updates its local in-memory route table on any change
This ensures that when a new microservice instance starts (or an instance dies), all other nodes in the cluster see the updated topology within seconds.
