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
IHttpClientor call HttpClient directly from the grain? - If I need a wrapper, would this implementation suffice?
- What's the difference between
GrainServiceandGrainServiceClient?
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
HttpClientwithin 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
GrainServiceto call into HTTP services from your grain: you can inject the required dependencies (IHttpClientFactoryor your typed client) and call HTTP services directly from grain code.Regarding the question on the purpose of
GrainServiceandGrainServiceClient,GrainServiceis a special service which is intended to be accessed from grain code.GrainServicesare 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 usingGrainServices. TheLocalReminderServiceon each node takes responsibility for a set of grains and will wake those grains up when their reminders are due.GrainServiceClientis used by grains to access theGrainServiceinstance which is responsible for the calling grain. The documentation explains more here: https://dotnet.github.io/orleans/Documentation/grains/grainservices.htmlI would avoid using
GrainServiceunless you find a use case which it fits closely.