Usually, it's recommended to create a wrapper implementing IHttpClient
or IHttpClientFactory
. Meanwhile, some Orleans samples show that it's ok to create HttpClient
instance on-demand and use it directly from the grain.
Questions
- Should I create Orleans service as a wrapper around
IHttpClient
or call HttpClient directly from the grain? - If I need a wrapper, would this implementation suffice?
- What's the difference between
GrainService
andGrainServiceClient
?
public interface IRemoteGrainService : IGrainService
{
Task<T> Get<T>(string source) where T : new();
}
public class RemoteGrainService : GrainService, IRemoteGrainService
{
private IGrainFactory _grainFactory;
private HttpClient _remoteService;
public RemoteGrainService(
IServiceProvider services,
IGrainIdentity id,
Silo silo,
ILoggerFactory
loggerFactory,
IGrainFactory grainFactory) : base(id, silo, loggerFactory)
{
_grainFactory = grainFactory;
}
public override Task Init(IServiceProvider serviceProvider)
{
return base.Init(serviceProvider);
}
public override async Task Start()
{
_remoteService = new HttpClient();
await base.Start();
}
public override Task Stop()
{
_remoteService.Dispose();
return base.Stop();
}
public Task<T> Get<T>(string source) where T : new()
{
return JsonConvert
.DeserializeObject<T>(
await _client.GetAsync(source))
.Content
.ReadAsStringAsync);
}
}
You should follow standard best-practices when using
HttpClient
within Orleans. The sample is creating a new one for simplicity of exposition, not as an indicator of best practices. A PR to change the sample documentation to useIHttpClientFactory
(for example) would likely be accepted.You do not need a
GrainService
to call into HTTP services from your grain: you can inject the required dependencies (IHttpClientFactory
or your typed client) and call HTTP services directly from grain code.Regarding the question on the purpose of
GrainService
andGrainServiceClient
,GrainService
is a special service which is intended to be accessed from grain code.GrainServices
are instantiated on every node in the cluster and each one is given responsibility over a set of grains. As an example, reminders (persistent timers) in Orleans are implemented usingGrainService
s. TheLocalReminderService
on each node takes responsibility for a set of grains and will wake those grains up when their reminders are due.GrainServiceClient
is used by grains to access theGrainService
instance which is responsible for the calling grain. The documentation explains more here: https://dotnet.github.io/orleans/Documentation/grains/grainservices.htmlI would avoid using
GrainService
unless you find a use case which it fits closely.