Service-fabric: Actor remoting with JSON

Created on 24 Aug 2018  路  19Comments  路  Source: microsoft/service-fabric

Is there a sample anywhere of how to do this?

I'm pretty sure V2 remoting supports this. The page below shows how it's done with services, but not for actors. Thanks.

https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-remoting

status-last6months type-doc-only

Most helpful comment

@amanbha I was afraid you were going to post something like that. I'm beginning to understand why you guys didn't provide a complete sample of a custom provider. At this point I'm left to wonder, what the next's landmine I'm going to merrily stroll over? Can I really be the first person to implement a JsonSerializationProvider for an ActorService and have the outlandish requirement of deleting actors?

All 19 comments

I've put together a solution which demonstrates what I'm trying to do. It looks like the actor call is being made, but it blows up when it tries to deserialize the arguments. Any suggestions? Thanks.

JsonActorSerialization.zip

Well there's a day I'll never get back. Here's a suggestion guys. If you're going to add functionality that allows you write your own serialization, you might as well provide the one everyone wants (JSON) out of the box. What you certainly don't want to do is provide a half baked hodge podge of code THAT DOESN'T WORK, as you've done here...

https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-remoting

And for anyone who does want to do JSON serialization for Actors, the code below does work. I know it does because I actually compiled, and wait for it, I RAN IT.

JsonSerializationExample.zip

Final tip for the Microsoft documentation guys... always provide a working solution nicely zipped up. Copying and pasting code into a help page almost ALWAYS loses vital information. Whether it's a namespace, and import, or even a tiny class, something gets lost.

Hi Jack,

Thanks for the feedback. The documentation feedback is a good one. Adding @amanbha and @suchiagicha for the feedback around Actors & Service Remoting.

@jackbond

Steps for plugin serialization remains the same as service remoting.
You dont need to write JsonFabricTransportActorRemotingProviderAttribute to make this work.

  1. You should write ServiceRemotingJsonSerializationProvider implementation (which you have already wrote it.)

  2. You need to plugin to your actor service .Need to write custom actor service and then override listener as pasted below.
    How to write custom actor service

    protected override IEnumerable CreateServiceReplicaListeners()
    {
    return new[]
    {
    new ServiceReplicaListener((c) =>
    {
    return new FabricTransportActorServiceRemotingListener(this, new ServiceRemotingJsonSerializationProvider());
    })
    };
    }

  3. You need to plugin on the client side in ActorProxyFactory .

    var proxyFactory = new ActorProxyFactory((c) =>
    {
    return new FabricTransportActorRemotingClientFactory(
    fabricTransportRemotingSettings: null,
    callbackMessageHandler: c,
    serializationProvider: new ServiceRemotingJsonSerializationProvider());
    });

In order to get faster response , please makes sure to open issues in actor repro (https://github.com/Microsoft/service-fabric-services-and-actors-dotnet/issues)

@suchiagicha I got it to work, but as I mentioned earlier, when demonstrating something as complex as this, code snippets are NOT helpful.

FYI, the code on this page is still broken. https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-remoting

Tucked away in one of Micah's comments is setting UseWrappedMessage to true. The documentation for that property is here, but it's rather useless because it doesn't document WHY you would use the property.

https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicefabric.services.remoting.fabrictransport.runtime.fabrictransportremotinglistenersettings.usewrappedmessage?view=azure-dotnet#Microsoft_ServiceFabric_Services_Remoting_FabricTransport_Runtime_FabricTransportRemotingListenerSettings_UseWrappedMessage

@jackbond There is no need to respond with that level of hostility.

https://opensource.microsoft.com/codeofconduct/

We also hit an issue with getting service fabric remoting to work with the newest JSON examples.
I do not know if it is the same issue @jackbond is seeing, but with .NET Core 2.1 apps the deserialization does not work, I assume due to runtime generated types. I have opened an issue here: JamesNK/Newtonsoft.Json#1808 .

A workaround for now could be something similar to

  class CustomSerializationBinder : DefaultSerializationBinder
  {
    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
      typeName = serializedType.FullName;
      assemblyName = serializedType.Assembly.FullName;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
      Type type = null;
      if (assemblyName.Contains("_.service.mt"))
      {
        var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == assemblyName || a.GetName().Name == assemblyName);
        type = assembly?.GetType(typeName);
      }
      return type ?? base.BindToType(assemblyName, typeName);
    }
  }

and adding this serialization binder to the serializer settings in ServiceRemotingRequestJsonMessageBodySerializer and ServiceRemotingResponseJsonMessageBodySerializer:

      serializer = JsonSerializer.Create(new JsonSerializerSettings()
      {
        TypeNameHandling = TypeNameHandling.All,
        SerializationBinder = new CustomSerializationBinder()
      });

@LasseNisted The sample I provide side steps that problem entirely by wrapping the arguments in an array instead of a dynamic class. The UseWrappedMessage property may actually side step the dynamic class problem as well, but I found it only after I got my solution going. I haven't attempted to see if setting IsWrappedMessage to true would resolve the issue. It SOUNDS like it might, but like I said, it's poorly documented.

@jackbond
I looked at your samples. You are not using same code as mentioned in public doc.
https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-remoting
For E.g : You are not inheriting from WrappedMessage in the implementation of JsonRemoteRequest.
I followed the instruction as mentioned in the public doc and created one sample for you.
Please have a look at it .
https://github.com/suchiagicha/Samples/tree/master/ActorCustomSerializer

As mentioned by @LasseNisted , json serializer won't work in .net core because of .net core issue mentioned by him.
Till the time , .NET team respond to the issue, you can use the workaround as mentioned by him.
Just want to clarify, we don't ship json serializer with service fabric remoting. We provide interfaces to users to plug-in their own serialization .

@suchiagicha To clarify, I started with the sample code, when it didn't work, I wrote code that did. The code that I provided in JsonSerializationExample.zip does not inherit from WrappedMessage because I wanted to explicitly control the serialization. As a result, my code does work with .net core 2.1.

In regards to not shipping a json serializer with service fabric remoting, I can understand not including that in the main package. However, there ought to be an official Microsoft.ServiceFabric.Services.Remoting.Json package so your developers aren't having to deal with the intricacies of serialization. I'd say this is especially true since the DocumentDb team refuses to mark their Geography classes with DataContract.

Finally, I downloaded your code ActorCustomSerializer, and it does not compile.

compilererror

@jackbond
I tried on different machine. It complied. My Guess is you are trying to download this into a network location ("Attempt to load assembly from network location" )as per the screenshot you pasted above and that is not working. Try to make that change and see if it works.

@suchiagicha My mistake, Windows security blocking the files because they came from an external zip. I was able to get your sample going, just wish it existed five days ago. :) Thanks for your help.

@jackbond @amanbha I've been following this thread because we're using JSON serialization in our implementation as well. However, we ran into problems when it comes to exceptions. Is there a way to customize the serialization of exceptions? I'm using the JSON serializer for messages, but anytime an exception is thrown by the actor it fails with a serialization exception:

System.Runtime.Serialization.SerializationException HResult=0x8013150C Message=Type 'SoftPro.Exceptions.Web.NotFoundException' in Assembly 'SoftPro.Exceptions, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable. Source=mscorlib StackTrace: at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)

It looks like the remoting framework is using the binary serializer for exceptions based upon this stack:

mscorlib.dll!System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(object obj, System.Runtime.Serialization.ISurrogateSelector surrogateSelector, System.Runtime.Serialization.StreamingContext context, System.Runtime.Serialization.Formatters.Binary.SerObjectInfoInit serObjectInfoInit, System.Runtime.Serialization.IFormatterConverter converter, System.Runtime.Serialization.Formatters.Binary.ObjectWriter objectWriter, System.Runtime.Serialization.SerializationBinder binder) Unknown mscorlib.dll!System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(object obj, System.Runtime.Serialization.ISurrogateSelector surrogateSelector, System.Runtime.Serialization.StreamingContext context, System.Runtime.Serialization.Formatters.Binary.SerObjectInfoInit serObjectInfoInit, System.Runtime.Serialization.IFormatterConverter converter, System.Runtime.Serialization.Formatters.Binary.ObjectWriter objectWriter, System.Runtime.Serialization.SerializationBinder binder) Unknown mscorlib.dll!System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(object graph, System.Runtime.Remoting.Messaging.Header[] inHeaders, System.Runtime.Serialization.Formatters.Binary.__BinaryWriter serWriter, bool fCheck) Unknown mscorlib.dll!System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(System.IO.Stream serializationStream, object graph, System.Runtime.Remoting.Messaging.Header[] headers, bool fCheck) Unknown Microsoft.ServiceFabric.Services.Remoting.dll!Microsoft.ServiceFabric.Services.Remoting.V2.RemoteException.FromException(System.Exception exception) Unknown Microsoft.ServiceFabric.Services.Remoting.dll!Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.FabricTransportMessageHandler.CreateFabricTransportExceptionMessage(System.Exception ex) Unknown Microsoft.ServiceFabric.Services.Remoting.dll!Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.FabricTransportMessageHandler.RequestResponseAsync(Microsoft.ServiceFabric.FabricTransport.V2.Runtime.FabricTransportRequestContext requestContext, Microsoft.ServiceFabric.FabricTransport.V2.FabricTransportMessage fabricTransportMessage) Unknown

We have some custom exceptions in our code that we are currently serializing using JSON and a custom JsonConverter. This works great for our stateless web APIs. I would like to be able to control the serialization of exceptions from Actors so I don't have to go through our code base and ferret out all our exception classes to ensure they are configured for binary serialization (e.g. with the [Serializable] attribute and ISerializable implementation).

Is this possible?

@suchiagicha We're attempting to delete an actor now, so I modified your ActorCustomSerializer as below to demonstrate the problem we're experiencing. The ActorId class doesn't play nicely with the JsonDeserializer. Is there any workaround for this, other than putting custom logic in the serialization provider to handle ActorId? Thanks.

        static void Main(string[] args)
        {
            while(true)
            {
                try
                {
                    ActorId actorId = ActorId.CreateRandom();

                    var proxyFactory = new ActorProxyFactory((c) =>
                    {
                        return new FabricTransportActorRemotingClientFactory(
                            fabricTransportRemotingSettings: null,
                            callbackMessageHandler: c,
                            serializationProvider: new ServiceRemotingJsonSerializationProvider());
                    });

                    var proxy = proxyFactory.CreateActorProxy<IActor1>(actorId, "fabric:/Application7");
                    var msg = new BaseType();
                    msg.j = 15;
                    proxy.SetMessageAsync(msg, CancellationToken.None).GetAwaiter().GetResult();
                    var res = proxy.GetMessageAsync(CancellationToken.None).GetAwaiter().GetResult();

                    var serviceProxyFactory = new ServiceProxyFactory((c) =>
                    {
                        return new FabricTransportServiceRemotingClientFactory(serializationProvider: new ServiceRemotingJsonSerializationProvider());
                    });

                    var partitionKey = new ServicePartitionKey(actorId.GetPartitionKey());
                    var serviceProxy = serviceProxyFactory.CreateServiceProxy<IActorService>(new Uri("fabric:/Application7/Actor1ActorService"), partitionKey);

                    serviceProxy.DeleteActorAsync(actorId, CancellationToken.None).Wait();

                    Console.WriteLine(res.j);

                    Debugger.Break();
                }

                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    Debugger.Break();
                }
            }
        }

@jackbond Looks like you are using Newtonsoft, so you will have to rely on custom converter for it which handles ActorId.

@amanbha I was afraid you were going to post something like that. I'm beginning to understand why you guys didn't provide a complete sample of a custom provider. At this point I'm left to wonder, what the next's landmine I'm going to merrily stroll over? Can I really be the first person to implement a JsonSerializationProvider for an ActorService and have the outlandish requirement of deleting actors?

@jackbond If you are plugging in your custom serializer than it will have to provide implementation to handle all types accepted and returned by apis accessed via Remoting. If you are using Newtonsoft, you will provide your own custom converter, If you are using some other serialization library, or some other serializtion fomat you will have to handle it there as well.
The V2 remoting layer provided a way to plugin any custom serializer, the implementation of custom serializer is something which you have to bring in.
By default all types provided by our framework supports DataContract serializer.

@amanbha At this point I understand the need to write custom code to handle the serialization of service fabric types. I've written one to handle ActorId, and perhaps as I try additional functions I'll be required to implement more. There are a couple things that are bothering me though. One, as I've said before, there should be a Microsoft.ServiceFabric.Services.Remoting.Json package which is a drop in replacement for DataContract serialization. Yes, you guys provided a remoting layer, but the practical reality is, 99.999% of the time, developers will be using JSON when they need something other DataContract. Two, the only reason I had to go through this agonizing exercise is that the CosmosDb team has standardized on JSON serialization, and doesn't mark their geography classes with DataContract. These are two fundamental Azure technologies, which ought to play nice with each other. Given the state of the industry, it seems like the necessary Service Fabric classes, like ActorId, ought to natively support JSON serialization.

@jackbond I understand the point which you making but given the fact that remoting layer allows you to bring in any serializer, the separate json package which you are suggesting would be nice for convenience, so it would be an enhancement feature request and the team need to triage it against other work items.
Actor and Service remoting code is open sourced at https://github.com/Microsoft/service-fabric-services-and-actors-dotnet . You can create a feature request for it there. Also please feel free to contribute to the project so that it can be part of future releases. Or you can release the additions which you are making for it as a separate nuget package which can be leveraged by the community.

Was this page helpful?
0 / 5 - 0 ratings