Cloud Code bindings with shared data models and logic between client and cloud code

Hello everyone, I’m currently working on a small little project and I’ve ran into a bit of an issue where shared models and logic are concerned, and I’m wondering how to resolve it, and what best practices around these issues might be.

I currently have a library (builds to .dll) that is referenced from both my Unity project and my Cloud Code solution. Inside of it I have a variety of data models, including enumerations for various things. Many of these data models also contain logic for things like validation. The idea was to have the same validation code be executable both client and server side.

So my project overall contains:

  1. SharedLibrary (containing models, enums, and validation logic)
  2. Unity Project (references SharedLibrary)
  3. Cloud Code Solution (references SharedLibrary)

An example of a data model might be the following

public enum PlayerType {
    Type1 = 1,
    Type2 = 2
    ...
}

public class PlayerData {
    public DateTime lastUpdate { get; private set; }
    public string profileName { get; private set; }
    public PlayerType type { get; private set; }

    public void setProfileName(string name) {
        // Validation code
    }
}

I ran into my problem the moment I generated the bindings for the cloud clode and noticed that my cloud code API bindings take the binding version of those data models, not the data models themselves (which I thought would be shared, but I suppose I was wrong). The result is that I cannot provide the data models from my shared library as parameters to the cloud code module functions I’ve exposed (despite them being the exact same models from the same dll), as it will reject it saying it cannot convert from one type to another (obviously because they are different since one is a generated binding).

So I suppose I have a two part question here.

  1. Is there a way to resolve this so that the models that I expose from my shared library can be used as the parameters for my cloud code module functions?
  2. What is the best practice with regards to this? Obviously my intent was to have the models and logic shared across both, from the same dll library, but wondering if that might not have been the best approach.

Thanks so much for your feedback!

My first thought was maybe I could just serialize the data model before sending it, then deseralize on the cloud code side since it’ll be identical. But again, not sure if this is the best way to approach it.

Hi Grinneh,

Sharing models between client and server via C# Modules is a solid approach. This helps keep your client and server models aligned.

What’s happening here is the Binding Generator can’t map directly to existing types in your shared .dll, so it generates new ones instead, which leads to the duplicate types and the conversion mismatch you’re seeing.

Because you’re already sharing the models, you can skip the generated bindings entirely and call your Cloud Code endpoint directly using the CallModuleEndpointAsync method from the Cloud Code SDK.

This method takes a Dictionary<string, object> into which you can pass your shared models. The SDK will serialize your shared objects to JSON when sending the request. As both your client and server reference the same .dll, the types will align on both ends.

If you still want a type-safe wrapper similar to the generated bindings, you can wrap CallModuleEndpointAsync as shown in the example here.

Let us know if you run into any issues with that approach.

1 Like

Hey mate, thanks for the response. Definitely moving in the right direction.

I gave your suggestion a go, and I’m getting response data back which is fantastic. However Unity seems to have an issue Deserializing the class. Currently I’m just getting a value from Cloud Save and returning it, so the deserialization failure at present seems to be on the client side with the following error:

 Unity.Services.CloudCode.Internal.Http.DeserializationException: Error converting value "{"SelectedCO":"None","LastSelectedCO":null,"ProfileName":"TestProfile"}" to type 'SharedLibrary.PlayerProfileData'. Path '', line 1, position 83.
   at Unity.Services.CloudCode.Internal.Http.JsonObject.GetAs[T] (Unity.Services.CloudCode.Internal.Http.DeserializationSettings deserializationSettings) [0x00038] in .\Library\PackageCache\com.unity.services.cloudcode\Runtime\com.unity.services.cloudcode.internal\Http\JsonObject.cs:94

For reference the class I’m using between client and server is the following:

[JsonObject(MemberSerialization.OptIn)]
public class PlayerProfileData: IBackendModel
{
    // Fields
    [JsonProperty] public BaseCOType SelectedCO { get; private set; } // Profile Picture ID
    [JsonProperty] public DateTime? LastSelectedCO { get; private set; }
    [JsonProperty] public string ProfileName { get; private set; } // Profile Name

    public PlayerProfileData(BaseCOType selectedCO, DateTime? lastSelectedCO, string profileName)
    {
        SelectedCO = selectedCO;
        LastSelectedCO = lastSelectedCO;
        ProfileName = profileName;
    }
}

The cloud code functions looks like the following

    [CloudCodeFunction("GetPlayerProfile")]
    public async Task<PlayerProfileData> GetPlayerProfile(IExecutionContext ctx, IGameApiClient gameApiClient)
    {
        try
        {
            // Get the existing value, if one exists
            var profile = await GetPlayerProfileOrNew(ctx, gameApiClient);

            // Serialize the profile
            //var serializedProfile = JsonConvert.SerializeObject(profile);
            
            // Return the result
            return profile;
        }
        catch (ApiException e)
        {
            throw new CoreServerException(
                $"GetPlayerProfile: Failed to get player profile for player {ctx.PlayerId}. Unknown error.",
                Strings.Error.InternalServerError,
                e);
        }
    }

And I’m calling it with the following on the client

PlayerProfileData result = await CloudCodeService.Instance.CallModuleEndpointAsync<PlayerProfileData>(
"CoreBackend",
"GetPlayerProfile",
new Dictionary<string, object>()
{
});
            
//var result = await _playerAPI.GetPlayerProfile();
 _lastResultText.text = $"Get: {result.ProfileName}";
Debug.Log(result);

Nothing jumps out at me as being particularly malformed from the above, but hoping a second pair of eyes can help me determine the issue. The data being returned comes from a Cloud Save I made earlier, and its JSON is correct.

A follow up

  1. Sorry I posted under a different account, my mistake :rofl:
  2. I redeployed the cloud code to the environment, just as a sanity check on my part just in case it was using the old one which had a different return type. Sure enough, it’s working! I guess I just needed to make sure that it uploaded first!

A couple of tips based on using the Unity Cloud Code for game back for about a year now:

  • Use ProtoBuf for data serialization between client/server. The JSON model won’t really work with rolling server updates, for example.

  • Use a single Cloud Code endpoint to process client commands. ProtoBuf supports inheritance so implementing this is trivial.

  • Implement command batching. We hit the Unity Cloud Call call limit quite early in our game development process.

  • We also implemented a system for persisting Cloud Call command batches on the client so they can be processed even if there’s a break in internet connectivity or if there’s an issue processing commands in the cloud.