Search code examples
orleans

IHttpClient implementation within Orleans.NET


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 and GrainServiceClient?
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);
  }
}

Solution

  • 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 use IHttpClientFactory (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 and GrainServiceClient, 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 using GrainServices. The LocalReminderService 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 the GrainService instance which is responsible for the calling grain. The documentation explains more here: https://dotnet.github.io/orleans/Documentation/grains/grainservices.html

    I would avoid using GrainService unless you find a use case which it fits closely.