Library implementation basics

Prerequisites

  • You understand the architecture of the SDK

  • You have generated an SDK and downloaded it with one of the supported package managers

  • You are able to locate endpoints and contracts of the SDK for imports

Introduction

You now have the SDK at hand and you're wondering how to get started. To utilise your endpoints you can use the generated Platform. You will need to start by initializing a PlatformSession that will work as a base for your actions. Depending on the needs of your specific service, you will then use the session to initialize a dispatcher, create forwarding sessions, accept gateway requests etc.

Our sessions come bundled with support for client-server usage and request forwarding tools for when you're working with multiple services at once. Interceptors make life easy for you defining exactly what needs to be implemented and what data you have in use. On top of that with pattern interceptors you can implement a bunch at a time. This gives you the opportunity to implement your authentication and transport mechanisms in a way that works for you.

You can use the library to test out and prototype your service interfaces ahead of building your service by using the generated sample data. When you have finished your implementation, you move from samples into proper implementations without having to change the interactions.

Initialize session

To get started you will first have to create your PlatformSession. It will allow you to easily write handlers for your endpoints and interact with them as a user of your defined functionality. Platform session is something you initialize once and keep for the lifetime of your application.

Starter for session initialisation. Create from builder and define interceptors.

using Uniscale.Designtime;

var session = await Platform.Builder()
   // Set up interceptors for the endpoints
   .WithInterceptors(i =>
   {
       // Implementation in later samples
   })
   // And build the session
   .Build();

While you can get started with just interceptors, the session initialization allows you to define more functionality on a general level.

using Uniscale.Designtime;

var session = await Platform.Builder()
    // Set up interceptors for the endpoints
    .WithInterceptors(i =>
    {
        // Implementation in later samples
    })
    // Define functionality for logging throughout
    .OnLogMessage(message => Console.WriteLine(message))
    // Inspect requested and returned objects
    .InspectRequests((o, ctx) => Console.WriteLine(o.ToString()))
    .InspectResponses((result, o, ctx) => Console.WriteLine(result.Value))
    // And build the session
    .Build();

Defining interceptors

Define the functionality of your endpoints inside the interceptors. You can intercept each function separately and have the data typed, or intercept whole namespaces and deal with untyped data.

Your interceptor's handle function has two parameters:

  1. The input model defined for your endpoint

  2. FeatureContext

    • This context will follow any request you make and contains the information about which solution, characters, language, data tenant and more this request was made with.

In a typical situation you can write interceptors for each endpoint

using Uniscale.Designtime;
using UniscaleDemo.Account_1_0.Functionality.ServiceToModule.
    Account.Registration.ContinueToApplication;
using UniscaleDemo.Account.Account;
using UniscaleDemo.Messages;

var session = await Platform.Builder()
    // Set up interceptors for endpoints
    .WithInterceptors(i =>
    {
        i.InterceptRequest(
            GetOrRegister.AllFeatureUsages,
            GetOrRegister.Handle((input, ctx) =>
                Result<UserFull>.Ok(UserFull.Samples().DefaultSample())));
        
        // You can also intercept with patterns
        i.InterceptMessage(
            Patterns.Messages.SendMessage.AllMessageUsages,
            Patterns.Messages.SendMessage.Handle((input, ctx) =>
            {
                // You can validate and use defined error codes on return
                if (string.IsNullOrEmpty(input.Message))
                    return Result.BadRequest(
                        ErrorCodes.Messages.InvalidMessageLength);
                Console.WriteLine(
                    ctx.Characters.Performer?.Terminology == "Term.Admin"
                        ? $"Admin sent message: {input.Message}"
                        : $"{input.By} sent message: {input.Message}");
                return Result.Ok();
            }));
    })
    .Build();

However, there are some cases where it makes more sense to have one implementation for a group of endpoints, like for example in your frontend when calling a specific backend service for all endpoints under the messages namespace. We stay out of your way and allow you to choose your preferred authentication and transport mechanisms.

using Uniscale.Core;
using Uniscale.Designtime;
using UniscaleDemo.Messages;

var session = await Platform.Builder()
    .WithInterceptors(i =>
    {
        // We can intercept the whole messages namespace with a pattern
        i.InterceptPattern(Patterns.Messages.Pattern, async (input, ctx) =>
        {
            // By creating a GatewayRequest JSON, the receiving end can simply
            // handle incoming calls with session.AcceptGatewayRequest()
            var json = GatewayRequest.From(input, ctx).ToJson();
            var responseJson = await SendToMessageService(json);
            // Convert JSON return from AcceptGatewayRequest
            return Result<object>.FromJson(responseJson);
        });
        
        // You can also intercept all (remaining) calls to a general endpoint
        i.InterceptPattern("*", async (input, ctx) =>
        {
            var request = GatewayRequest.From(input, ctx);
            var responseJson = await CallGeneralServiceEndpoint(request);
            return Result<object>.FromJson(responseJson);
        });
    })
    .Build();

Handle endpoints and make requests

With the platform session you have a setup that knows how to handle your endpoints. To utilise that and call your endpoints you need to get a DispatcherSession through which you can interact with your endpoints. Dispatchers are designed to be set up in a way where one dispatcher can handle all your solution/service's endpoints.

Making a request: PlatformSession -> DispatcherSession -> Request()

Platform session can also be used to receive requests. If in your endpoint you can turn your data into a GatewayRequest (or you sent the data with our helpers), you use AcceptGatewayRequest in your session and let the platform handle the request based on your interceptor implementations.

Receiving a request: PlatformSession -> AcceptGatewayRequest()

The typical case for making a request and creating a GatewayRequest from it.

using Uniscale.Core;
using Uniscale.Designtime;
using UniscaleDemo.Account_1_0.Functionality.ServiceToModule.
    Account.Registration.ContinueToApplication;

var session = await Platform.Builder()
    // Set up your PlatformSession as in above samples
    // For example sending all to GeneralServiceEndpoint
    .WithInterceptors(i =>
    {
        i.InterceptPattern("*", async (input, ctx) =>
        {
            var request = GatewayRequest.From(input, ctx);
            var responseJson = await CallGeneralServiceEndpoint(request);
            return Result<object>.FromJson(responseJson);
        });
    })
    .Build();

// Initialize a DispatcherSession with a solution id of your choice
var dispatcher = session
    .AsSolution(Guid.Parse("a18b2b3e-4010-4c0f-a01f-565fda8c466e"));

// Create a new user and check if we got a successful response
var result = await dispatcher
    .Request(GetOrRegister.With("myAwesomeUserHandle"));
if (result.Success)
    Console.WriteLine(
        "User: id=" + result.Value.UserIdentifier +
        " Handle=" + result.Value.Handle);
else
    Console.WriteLine("Failed with: " + result.Error.ToLongString());

And a quick sample on how one would receive such request.

using Uniscale.Core;
using UniscaleDemo.Account_1_0.Functionality.ServiceToModule.
    Account.Registration.ContinueToApplication;
using UniscaleDemo.Account.Account;

var session = await Platform.Builder()
    // Implement what happens on endpoints when GatewayRequests come in.
    .WithInterceptors(i => i
        .InterceptRequest(
            GetOrRegister.AllFeatureUsages,
            GetOrRegister.Handle((input, ctx) =>
                Result<UserFull>.Ok(UserFull.Samples().DefaultSample()))))
    .Build();


// In your endpoint handler (Http endpoint or similar)
var result = await session.AcceptGatewayRequest(requestJson);
return result.ToJson();

You can use the created dispatcher to make multiple requests and fetch Terminologies that you have defined into your model. You can find a more complex sample of how forwarding works in Forwarding session basics.


Conclusion

This tutorial has information on everything you need to consider when getting started with your minimalistic implementation. If you want to see all of it come together, you can find samples based on our Demo solution in: https://github.com/uniscale?q=demo-. You can test it out, mix and match frontend and backend demos as you want.

Last updated