Microservices - Content
Create and deploy server-authoritative C# [CLCP-Microservices-05]
Beamable Microservices supports references to various built-in and custom data types. Some situations require additional consideration.
Warning!
Here are hints to help explain some of the trickier concepts:
- Microservices does not support passing/returning any child of
UnityEngine.Object
. This is by design- Microservices does not support passing/returning any child of
Beamable.Common.Content.ContentObject
. This is a serialization limitation which may be resolved in the future. See below for a workaround
Easy Setup 💚
Passing/returning no values requires no particular setup.
namespace Beamable.Server
{
[Microservice("MyCustomMicroservice")]
public class MyCustomMicroservice : Microservice
{
[ClientCallable]
public async void MyMethod01()
{
}
}
}
Intermediate Setup 🔵
Passing/returning built-in C# primitive data types requires simply adding the appropriate "using" statements.
using System.Threading.Tasks;
namespace Beamable.Server
{
[Microservice("MyCustomMicroservice")]
public class MyCustomMicroservice : Microservice
{
[ClientCallable]
public async Task<int> MyMethod01(string myMessage)
{
return 10;
}
}
}
Advanced Setup ⚫
Passing/returning custom C# primitive data types requires both...
- Adding the appropriate "using" statements
- Proper management of Unity Assembly Definitions Files (Asmdef).
using System.Threading.Tasks;
using Examples.MyCustomContentMicroserviceExample.Shared.MyCustomContent;
namespace Beamable.Server
{
[Microservice("MyCustomMicroservice")]
public class MyCustomMicroservice : Microservice
{
[ClientCallable]
public async Task<MyCustomData> MyMethod01()
{
}
}
}
Why Asmdef?
During the maturity of the Unity product, the concept of Asmdef was added. Asmdefs continue to be an optional feature for some Unity projects. In this situation it is required.
See Learning Fundamentals for more info on Asmdefs.
Unity C# Code execution is designed with key principles
- A typical Unity project may have none, some, or all of its C# code sitting within Asmdefs
- C# code not inside an Asmdef can access any code
- C# code inside an Asmdef can access only code inside an Asmdef. The rules are set by configuring each Asmdef
- An Asmdef cannot make any circular references
Beamable C# Microservices is designed with key principles
- Microservices run in a unique server environment instead of the typical Unity client environment
- Microservices live in an Asmdef so limit which code a Microservice can access. This is safer and more appropriate scoping
Example
Here are examples which cover common programming needs.
Beamable SDK Examples
• The following example code is available for download at GitHub.com/Beamable_Microservices_Examples
1. Asmdef Setup
The requirement here is for each Asmdef to reference only the other Asmdef(s) it must reference. Depending on the complexity of your project, you may have more or less total Asmdefs than are shown here.
Do Not "Use GUIDs"
Unity's Assembly Definition References have an optional flag for "Use GUIDs". This is not currently supported by Beamable Microservices, so it is advised to leave this box unchecked.
The Project's Asmdefs
The Project's Asmdef References
The interrelation between some of the Asmdefs is shown here. This is only part of the story. It is strongly recommended to download this project and view each of these 4 Asmdefs within the Unity inspector for more info.
From | To: AutoGen | To: Runtime | To: Server | To: Shared |
---|---|---|---|---|
AutoGen | ✔️ | |||
Runtime | ✔️ | ✔️ | ||
Server | ✔️ | |||
Shared |
2. Code Setup
Data Design
Let's assume a C# Microservice method is designed to return a MyCustomDataContent
data type. However, due to the limitations discussed above, that is not currently possible.
The workaround is to populate MyCustomDataContent
with a MyCustomData
data type which holds all the data of interest. The Microservice can properly fetch the content yet return only data within. The proper data design is shown here.
using System;
using Beamable.Common.Content;
namespace Examples.MyCustomContentMicroserviceExample.Shared.MyCustomContent
{
[ContentType("my_custom_content")]
public class MyCustomContent : ContentObject
{
public MyCustomData MyCustomData = new MyCustomData();
}
[Serializable]
public class MyCustomData : System.Object
{
public int Attack;
public int Damage;
}
}
Calling MyCustomContentMicroservice
Here the example calls to MyCustomContentMicroservice
which returns the MyCustomData
.
private async void SetupBeamable()
{
var beamableAPI = await Beamable.API.Instance;
Debug.Log($"beamableAPI.User.id = {beamableAPI.User.id}");
_myCustomContentMicroserviceClient = new MyCustomContentMicroserviceClient();
// #1 - Call Microservice
MyCustomData myCustomData =
await _myCustomContentMicroserviceClient.LoadCustomData();
// Hardcoded guess. Feel free to update via Content Manager
bool isSuccess = myCustomData.Attack == 22;
// #2 - Result
Debug.Log ($"LoadCustomData() myCustomData.Attack = {myCustomData.Attack}, Success={isSuccess}");
}
Implementing MyCustomContentMicroservice
Here the Content
service returns the MyCustomContent
and the Microservice returns just the MyCustomData
within.
using System.Threading.Tasks;
using Examples.MyCustomContentMicroserviceExample.Shared.MyCustomContent;
namespace Beamable.Server
{
[Microservice("MyCustomContentMicroservice")]
public class MyCustomContentMicroservice : Microservice
{
[ClientCallable]
public async Task<MyCustomData> LoadCustomData()
{
MyCustomData myCustomData = null;
// Check All Content
var clientManifest = await Services.Content.GetManifest();
foreach (var entry in clientManifest.entries)
{
// Find Matching Type
if (entry.contentId.Contains("my_custom_content"))
{
// Load MyCustomContent
MyCustomContent myCustomContent =
await Services.Content.GetContent<MyCustomContent>(entry.ToContentRef());
myCustomData = myCustomContent.MyCustomData;
}
}
// Return data
return myCustomData;
}
}
}
Content examples
Here are examples which cover common programming needs.
Beamable SDK Examples
• The following example code is available for download at GitHub.com/Beamable_Microservices_Examples
Calling MyCurrencyMicroservice
The key methods here return a bool
to determine the success of the operation. As an alternative game makers can choose to return additional information such as the affected currencyId or the new currency amount:
private async void SetupBeamable()
{
var beamContext = BeamContext.Default;
await beamContext.OnReady;
Debug.Log($"beamContext.PlayerId = {beamContext.PlayerId}");
_myCurrencyMicroserviceClient = new MyCurrencyMicroserviceClient();
// #1 - Call Microservice
bool isSuccess1 = await _myCurrencyMicroserviceClient.AddToActiveCurrency();
Debug.Log ($"AddToActiveCurrency() isSuccess = {isSuccess1}");
// #2 - Call Microservice
bool isSuccess2 = await _myCurrencyMicroserviceClient.RemoveFromActiveCurrency();
Debug.Log ($"RemoveFromActiveCurrency() isSuccess = {isSuccess2}");
}
Implementing MyCurrencyMicroservice
Here 1
is added to the current currency amount.
Security
- Notice that
AddToActiveCurrency()
accepts no arguments. This is more limiting but also more secure- Game makers may pass arguments to Microservice methods, but be wary of the security implications of an alternative such as
AddToActiveCurrency(contentId, amountToAdd)
which could be called repeated by malicious clients
[ClientCallable]
public async Task<bool> AddToActiveCurrency()
{
// Add Value
long amountDelta = 1;
// Change the amount of currency
bool isSuccess = false;
string currencyId = await GetActiveCurrencyId();
long amountOld = await GetActiveCurrencyValue();
if (!string.IsNullOrEmpty(currencyId))
{
long amountNew = amountOld + amountDelta;
await Services.Inventory.SetCurrency(currencyId, amountNew);
// Validate
long amountConfirmed = await Services.Inventory.GetCurrency(currencyId);
isSuccess = amountConfirmed == amountNew;
}
return isSuccess;
}
Content Preloading
As of Beamable 1.8.0, every microservice instance downloads the entire content manifest before it starts accepting HTTP traffic. This feature can be disabled by disabling the EnableEagerContentLoading
flag on the MicroserviceAttribute
. This code snippet would create a microservice that would not pre-download content. Be aware that this means the first time the Microservice needs to fetch content can result in long request times.
[Microservice("example", EnableEagerContentLoading = false)]
public class Example : Microservice
{
}
Updated about 1 year ago