What is RPC?
RPC (Remote Procedure Call) is an inter-process communication mechanism that allows you to call a remote service as if it were a local function. An RPC framework hides the underlying transport (TCP/UDP), serialization format (XML/JSON/binary), and network details, so callers only need to know what remote interface exists and where — not how it works under the hood.
Key characteristics:
- Hides network communication details (no direct Socket or HTTP handling)
- Request/response model: client sends a request, server returns a response
- The calling experience mirrors a local function call
An RPC framework consists of three roles:
- Service Provider: runs on the server side; defines and implements the service interface
- Service Publisher: runs on the RPC server; publishes the local service as a remote service for consumers
- Local Service Proxy: runs on the RPC client; proxies calls to the remote provider and wraps the result
How Silky RPC Works
Silky uses DotNetty as the transport layer and implements RPC over persistent TCP connections. The key technical challenges it addresses:
- Remote service definition: Services are defined as C# interfaces annotated with
[ServiceRoute]. Consumers reference the interface project (or NuGet package) to gain access to service metadata. - Dynamic proxy: Implemented via
Autofac.Extras.DynamicProxy— injecting an interface automatically generates a local dynamic proxy that wraps remote calls transparently. - Transport: Silky uses DotNetty as the underlying communication framework; the RPC protocol is transport-agnostic.
- Serialization: Objects are serialized to binary for network transfer. Silky uses JSON by default and also supports MessagePack and ProtoBuf.
Calling a Remote Service
Via Interface Proxy (Recommended)
In practice, the service definition (interface) and implementation (class) live in separate assemblies so the interface can be referenced by other services.
Step 1: Define the application service interface in a separate project:
namespace Demo.Contracts;
[ServiceRoute]
public interface IOrderAppService
{
Task<OrderOutput> CreateAsync(CreateOrderInput input);
Task<OrderOutput> GetAsync(long id);
}
Step 2: The consumer references the interface project (or its NuGet package).
Step 3: Inject and use the interface — Silky generates the dynamic proxy automatically:
public class ShoppingCartAppService : IShoppingCartAppService, IScopedDependency
{
private readonly IOrderAppService _orderService; // remote interface
public ShoppingCartAppService(IOrderAppService orderService)
{
_orderService = orderService;
}
public async Task<string> CheckoutAsync(CheckoutInput input)
{
var order = await _orderService.CreateAsync(new CreateOrderInput
{
ProductId = input.ProductId,
Quantity = input.Quantity,
});
return order.Id.ToString();
}
}
Service Entry ID
Every method on a service interface generates a unique Service Entry ID used to route RPC calls:
ServiceEntryId = FullyQualifiedMethodName + ParameterNames + _ + HttpVerb
Example — the GetAsync(long id) method on IOrderAppService generates:
Demo.Contracts.IOrderAppService.GetAsync.id_Get
Governance Options
Every service entry inherits from the global Governance configuration and can be overridden via [Governance] attribute:
[ServiceRoute]
public interface IOrderAppService
{
[Governance(TimeoutMillSeconds = 3000, RetryTimes = 2)]
Task<OrderOutput> CreateAsync(CreateOrderInput input);
[HttpGet("{id}")]
[Governance(ShuntStrategy = ShuntStrategy.HashAlgorithm)]
Task<OrderOutput> GetAsync([HashKey] long id);
}
RPC Security
All internal RPC communication is protected by a shared rpc.token:
rpc:
token: "a-shared-secret-known-only-to-cluster-services"
isSsl: false # set true to enable SSL/TLS
External callers cannot access RPC ports directly — they must go through the API gateway.
Serialization
| Format | Package | Notes |
|---|---|---|
| JSON | Built-in (default) | Human-readable, best compatibility |
| MessagePack | Silky.Rpc.MessagePack | Compact binary, high performance |
| ProtoBuf | Silky.Rpc.ProtoBuf | Schema-based, cross-language |
Configure via:
rpc:
codec: Json # Json | MessagePack | ProtoBuf
