Injecting stateless service in Stateful service in service fabric

1.2k views Asked by At

I have stateful service that process data in the reliable queue (inside a while loop). The processing of data actually calls a stateless service.

Since a stateless service can move from one node to another, is it safe that I inject this dependency (stateless service) in the constructor of the stateful service? Just want to make sure that the dependency initialized/injected in the constructor is not fix to one node.

2

There are 2 answers

4
LoekD On

If you're using remoting to communicate with the stateless service, you can inject the IServiceProxyFactory in the constructor. This way you can also inject a Mock for test-purposes.

The question changed after this answer. Additional info: Service remoting handles resolution of service addresses, connection, retry, and error handling

example:

    public class MyStatefulService : StatefulService
    {
        private readonly Uri CalledServiceName = new Uri("fabric:/MyApp/MyStatefulService");
        private readonly IServiceProxyFactory ServiceProxyFactory;  

        public MyStatefulService(StatefulServiceContext serviceContext, IReliableStateManagerReplica reliableStateManagerReplica, IServiceProxyFactory serviceProxyFactory = null)
           : base(serviceContext, reliableStateManagerReplica)        
        {       
            ServiceProxyFactory = serviceProxyFactory ?? new ServiceProxyFactory();
        }

        public Task InsertAsync(object value)
        {
            var serviceProxy = ServiceProxyFactory.CreateServiceProxy<IMyStatefulService>(CalledServiceName);        
            return serviceProxy.InsertAsync(value);
        }
    }
0
yoape On

TL;DR; Yes, you can, the service remoting client handles that scenario for you in most cases.

Explained: When you create a Proxy to communicate with a service using service remoting you get a client that is capable of retrying communication with the actual service. There is some description about this in the documentation (https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-connect-and-communicate-with-services), especially the section that describes Service discovery and resolution:

Resolving and connecting to services involves the following steps run in a loop:

  • Resolve: Get the endpoint that a service has published from the Naming Service.
  • Connect: Connect to the service over whatever protocol it uses on that endpoint.
  • Retry: A connection attempt may fail for any number of reasons, for example if the service has moved since the last time the endpoint address was resolved. In that case, the preceding resolve and connect steps need to be retried, and this cycle is repeated until the connection succeeds.

The last part there is very relevant to your question, if the service has moved since the last time, it actually retries to Resolve and then Connect. So, this means that if you are using fabric transport to remote to your services there is actually a built in mechanism to handle just this scenario.

When you call the default implementation of IServiceProxyFactory (Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxyFactory) you get a ServiceProxy that communicates using a IServiceRemotingClient. In the Retry part of that IServiceRemotingClient, the IExceptionHandler associated with that retries both Transient and Non-transient errors if they are related to known fabric errors. One of those being that the address of the service somehow changed.

There is a ease-of-use static ServiceProxy class (used through ServiceProxy.Create<...>), this one really only uses Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxyFactory under the hood. As noted by @LoekD in his answer, the approach where you inject an instance of IServiceProxyFactory in the constructor helps you in testing. In live code you inject an instance of Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxyFactory, and in testing you provide your own mock of it. In your code you use it similar to the static ServiceProxy, e.g. _serviceProxyFactory.Create<IMyService>(...).

Note, if you on the other hand implement your own IServiceRemotingClient then you need to consider retrying various fabric exceptions (such as FabricNotReadableException and FabricTransientException. Check https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-errors-and-exceptions for more details on common exceptions in FabricClients.