Microservice Routing

Calling a function on a Microservice

Dependencies

Before you can configure Beamable Standalone Microservices, you need to complete the Getting-Started Guide. That means having Dotnet 8 installed, and getting the Beam CLI.

You can confirm you have everything installed checking the versions of the tools.

dotnet --version
beam version # beam --version also works.

In order to configure a Microservice, you also need to have a local .beamable workspace with a Beamable Standalone Microservice. As a reminder, you can create one quickly using the commands below.

beam init MyProject
cd MyProject
beam project new service HelloWorld

Microservice Routing

Beamable Microservices use a privileged web socket to communicate with Beamable's existing APIs and services. There is a custom Beamable application level protocol, Thorium, that allows a Microservice to receive HTTPS traffic sent to https://api.beamable.com. However, for the Microservice to receive the traffic, the HTTP request needs to meet the following requirements,

  1. The uri of the HTTP request must have a path that maps to the desired Microservice.
  2. The HTTP request must have an X-DE-SCOPE header, and
  3. Optionally, the HTTP request should have an Authorization header that carries a Bearer Token.

Path Routing

The uri of the HTTP request destined for a Microservice must follow a strict format. The format is given by the code snippet below.

var uri = 'https://' + host + '/basic/' + cid + '.' + pid + '.' + localPrefix + 'micro_' + serviceName + '/' + method;

The variables are described in the following table.

VariableRequiredExampleDescription
hostyesapi.beamable.comIn most cases, the host is always api.beamable.com. However, if you have a Beamable Private Cloud arrangement, this may vary.
cidyes1338004997867618The Customer Id, or CID, is the ID that defines your organization apart from other Beamable developers.
pidyesDE_1752011665993728The Project Id, or PID, is the ID that defines your Realm.
localPrefixnoMacbook-ProA Microservice may run locally on a developer's machine, and it may run remotely deployed to the Beamable Cloud. In this scenario, the two instances should not receive traffic from the same origin. Thus, a prefix is injected into the routing to distinguish between the two services.
serviceNameyesHelloWorldThe name of your service. This maps to the Beam Id of your service.
methodyesAddThe name of the Method you want to invoke.

When the host (api.beamable.com) receives an HTTP request with the following uri format, the request will be deconstructed into the variable components, and the contents of the HTTP request will be forwarded to your Microservice via the Thorium web socket protocol. The Microservice receives the request, and uses the method component of the original request to invoke the right [Callable] method.

X-DE-SCOPE Header

In addition to specifying the cid and pid in the uri of the HTTP request, those values must also be sent in a special HTTP header, X-DE-SCOPE. The value for this header should take the format,

var scope = cid + '.' + pid;

Authorization

Finally, while not required, it is important to send an HTTP authorization header in the form of a Bearer token. The bearer token should be a valid access token for a Beamable Player. These tokens can be fetched from the Portal, or you can use the following command to view the token information from a local beamable CLI project.

cat .beamable/temp/connection-auth.json

Beamable does not require that any access token be provided via an authorization header. However, if an authorization header is provided, then Beamable will decorate the request with account information before the request is forwarded to the Microservice. The Microservice itself may enforce the account details to have minimal access permissions. These constraints vary based on the type of [Callable] attribute you use.

The account information is accessible via the Context.UserId property when executing the [Callable] method.

AttributeAuthorization Requirements
[Callable]None. This method may be invoked when no Authorization is provided. Context.UserId will yield 0.
[ClientCallable]A valid player access token is required, and the Context.UserId will yield the calling player's player Id.
[AdminOnlyCallable]An access token for a player with the admin role.
[ServerCallable]This may be used by requests authenticated with signed requests. However, no player is present.

Calling Microservice Code From Unity

Microservices can automatically generate client code for the Unity game engine. First, a Unity project needs to be linked to the .beamable workspace. To do this, use the project add-unity-project command.

beam project add-unity-project <relative-path-to-unity-project>

The given path should be the relative path to the Unity project. If it isn't right, the CLI will offer an explorative search flow to identify a valid Unity project.

After the command has run, there will be a .beamable/linked-projects.json file. You can review it to double check your project has been added correctly.

MyProject % cat .beamable/linked-projects.json 
{
  "unityProjectsPaths": [
    "../UnityProject"
  ],
  "unrealProjectsPaths": []
}%  

When there is a linked project, anytime a Microservice builds, it will automatically generate client code for the Unity project to use. This can be accessed via the BeamContext.

public async Promise TalkToMicroservice(){
	var ctx = await BeamContext.Default.Instance;  
	var client = ctx.Microservices().HelloWorld();  
	var sum = await client.Add(1, 2);
}

The automatic client code generation can be disabled when a project builds by modifying the <GenerateClientCode> option. Learn more in the configuration guide.

Custom Clients

It is possible to use the project oapi command to generate an Open API document and then use open source tools to transpile the document into a client in some other programming language.

The following command will output the Open API document for the service into a file called doc.json.

beam project oapi --ids HelloWorld | jq '.data.openApi | fromjson' > doc.json

In fact, that command can baked into the Microservice's .csproj file with a custom build target. This requires that the <SolutionDir> property is set, which only happens when you run the project from the IDE. See the generate-props command to extend the <SolutionDir> property outside of IDE use cases.

<Target Name="Build Local OpenAPI File" AfterTargets="Build">  
    <Exec Command="$(BeamableTool) project oapi --ids HelloWorld | jq '.data.openApi | fromjson' > $(SolutionDir)local/doc.json"/>  
</Target>

Then, you can use the open source Open API Generator to build a local javascript client. This snippet uses docker to interact with the generator tool, but you can also use npm.

docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
	-i /local/doc.json \
	-g javascript \
	--additional-properties=usePromises=true \
	-o /local/out/js

In this example, we need to use browserify to convert the generated client code into a valid browser script.

npm install -g browserify
browserify ./dist/index.js --standalone helloWorld > ../../app/bundle.js 

Then, a sample web page might use a similar script to interact with the Microservice.

<html>
    <body>
        <script src="bundle.js"></script>

        <script>
            var cid = 'cid';
            var pid = 'pid';
            var refreshToken = 'redacted';
            var localPrefix = 'local';
            var serviceName = 'HelloWorld';

            var host = 'https://api.beamable.com/basic/' + cid + '.' + pid + '.' + localPrefix + 'micro_' + serviceName;
            var client = new helloWorld.ApiClient(host);
            client.authentications['scope'].apiKey = cid + '.' + pid;
            client.authentications['user'].accessToken = refreshToken
            
            var api = new helloWorld.UncategorizedApi(client);
            var args = new helloWorld.AddRequestArgs();
            args.a = 2;
            args.b = 3;

            var opts = {
                'addRequestArgs': args
            };
            api.addPost(opts).then(function(result) {
                console.log('got result', result)
            });

        </script>
    </body>
</html>```