Overview
The Executor is the core dispatch component when a service entry is invoked. It decides whether the current request should be handled by the local instance or forwarded via RPC to another service instance.
| Component | Interface | Default Implementation | Responsibility |
|---|---|---|---|
| Unified entry point | IExecutor | DefaultExecutor | Dispatches by ServiceEntry / ServiceEntryDescriptor |
| Local executor | ILocalExecutor | DefaultLocalExecutor | Resolves local implementation, runs server filter pipeline + business method |
| Remote executor | IRemoteExecutor | DefaultRemoteExecutor | Builds RPC message, applies Polly policies, sends via DotNetty |
IExecutor — Unified Dispatch Entry
DefaultExecutor routes calls to the correct execution path:
public class DefaultExecutor : IExecutor
{
// When ServiceEntry is available (IsLocal flag already determined):
public async Task<object> Execute(ServiceEntry serviceEntry, object[] parameters, string serviceKey = null)
{
// serviceEntry.Executor is a pre-built delegate that captures the local/remote decision
return await serviceEntry.Executor(serviceKey, parameters).ConfigureAwait(false);
}
// When only ServiceEntryDescriptor is available (no local impl — always remote):
public async Task<object> Execute(ServiceEntryDescriptor serviceEntryDescriptor,
IDictionary<ParameterFrom, object> parameters, string serviceKey)
{
var remoteExecutor = EngineContext.Current.Resolve<IRemoteExecutor>();
return await remoteExecutor.Execute(serviceEntryDescriptor, parameters, serviceKey).ConfigureAwait(false);
}
}
ServiceEntry.Executor — Local/Remote Decision Delegate
ServiceEntry builds its Executor delegate at construction time:
// Inside ServiceEntry constructor
Executor = CreateExecutor();
private Func<string, object[], Task<object>> CreateExecutor()
{
return IsLocal
? CreateLocalExecutor() // local: delegates to ILocalExecutor
: CreateRemoteExecutor(); // remote: delegates to IRemoteExecutor
}
IsLocal is true if the current assembly contains a non-abstract implementation class for the service interface. Once set at startup, it does not change at runtime.
Local Execution Path
ServiceEntry.Executor (IsLocal = true)
│
▼
DefaultLocalExecutor.Execute()
│ Resolve implementation instance (by serviceKey for multi-impl routing)
│
▼
IServerLocalInvokerFactory.CreateInvoker()
│ Build LocalInvoker with server filter chain
│
▼
LocalInvoker.InvokeAsync()
│ Run server filter pipeline → invoke business method
│
▼
Return result
See Local Executor & Server-Side Filters for details.
Remote Execution Path
ServiceEntry.Executor (IsLocal = false)
│
▼
DefaultRemoteExecutor.Execute()
│ Build RemoteInvokeMessage
│
▼
IInvokePolicyBuilder.Build()
│ Compose Polly policy: Timeout → Retry → CircuitBreaker → Fallback
│
▼
policy.ExecuteAsync( IRemoteCaller.InvokeAsync() )
│ 1. Find healthy endpoints for target service
│ 2. Select endpoint by load balancing strategy
│ 3. Get/reuse DotNetty TransportClient
│ 4. Run client filter pipeline
│ 5. Send RPC message, await response
│
▼
Parse response, return result
See Remote Executor & RPC Call Chain for details.
IInvokeTemplate — Direct Cross-Service Calls
For scenarios where you only have a ServiceEntryDescriptor (not a full ServiceEntry), IInvokeTemplate provides a programmatic API for calling any service entry by ID:
var result = await _invokeTemplate.InvokeAsync<OrderDto>(
serviceEntryId: "IOrderAppService.GetByIdAsync.id.GET",
parameters: new Dictionary<ParameterFrom, object>
{
[ParameterFrom.Path] = new { id = 42 }
});
This always routes through IRemoteExecutor.
