How to create a microservice

Set the table, van Gogh

Microservices in nyssr.net are usually smaller than the variants in the restful ecosystem. The reason for this is that very many microservices can be run by a single node.

Microservices can also communicate in all directions, a consequence of the way in which messaging is used. They can also be factories to create instances of services that communicate exclusively with an owner.

Therefore, a microservice always consists of at least one target so that it can communicate.

In order for other parts of code to find the microservice, a microservice registers with a microservice registry. Multiple registries can exist that automatically synchronize their datasets. This ensures that this important part of nyssr.net is redundant and resistant to malfunction.

Microservice registries use broadcasts to ensure that each new node is informed about the existing registries. A local plugin collects this information and provides services related to the registration and deregistration of local microservices.

Therefore, it is very easy to write a microservice. It is sufficient to create a target, which, after registration at the target registry of a namespace, also registers at the microservice registry. Before the target is terminated, the microservice is deregistered by another call.

    class CMyMicroserviceTarget extends CTarget implements IService
{
    private static final IId MICROSERVICE_ID = CIdFactory.fromObject("MyLovelyMicroservice");
    // A random instance id
    private static final IId INSTANCE_ID = CIdFactory.randomOfType(EIdType.UUID);
    private final IDependencies mDependencies;

    CMyMicroserviceTarget(@NotNull final IDependencies aDependencies)
    {
        mDependencies = aDependencies;

        addMessageHandler(CRecordStartTarget.ID,
                          this::asyncStartTarget);
        addMessageHandler(CRecordDismiss.ID,
                          this::asyncDismiss);
    }

    @Override
    public void activate(@NotNull final IServiceRegistry aServiceRegistry) throws Exception
    {
        final IId namespaceId = CIdFactory.random("MyMicroservice");
        final INamespace ns = mDependencies.getNamespaceFactory()
                                           .createAndRegisterNamespace(namespaceId,
                                                                       "My Microservice Namespace");
        ns.getTargetRegistry()
          .registerTarget(this);
    }

    @Override
    public void deactivate(@NotNull final IServiceRegistry aServiceRegistry) throws Exception
    {
        deregisterMicroservice();
        deregisterTarget();
    }

    private boolean asyncStartTarget(@NotNull final CEnvelope aEnvelope,
                                     @NotNull final CRecord aRecord) throws CException
    {
        if (aEnvelope.isAnswer())
        {
            return false;
        }
        else
        {
            registerMicroservice();
            aEnvelope.setResultSuccess();
            return true;
        }
    }

    private boolean asyncDismiss(@NotNull final CEnvelope aEnvelope,
                                 @NotNull final CRecord aRecord) throws CException
    {
        if (aEnvelope.isAnswer())
        {
            return false;
        }
        else
        {
            deregisterMicroservice();
            deregisterTarget();
            aEnvelope.setResultSuccess();
            return true;
        }
    }

    private void registerMicroservice() throws CException
    {
        // A list of records that belong to my API
        final List<CDescriptionOfRecord> api = new ArrayList<>();
        api.add(new CDescriptionOfRecord(CRecordDismiss.ID,
                                         "Dismiss this service"));

        mDependencies.getHelperForLocalMicroServices()
                     .registerMicroService(MICROSERVICE_ID,
                                           "My lovely microservice",
                                           api,
                                           INSTANCE_ID,
                                           getAddress());
    }

    private void deregisterMicroservice() throws CException
    {
        mDependencies.getHelperForLocalMicroServices()
                     .deregisterMicroService(MICROSERVICE_ID,
                                             INSTANCE_ID);
    }
}

The microservice class implements the interface IService to get the benefit of the activate() and deactivate() methods. The two methods are called by the package initializer. See also Package Initialization.

In the activate() method we will create a namespace in which we will register this target. During registration, a list of records belonging to the microservice API is specified. We have given the message CRecordDismiss as an example here. This makes little sense, but demonstrates the procedure.

The first message we get asynchronously is the CRecordStartTarget message. When the message arrives, we already have a valid target address. We use this address to register the microservice.

Before we deregister the target, we deregister from the microservice registry.

We have two dependencies: The namespace factory for creating namespaces (present in the kernel) and the microservice helper (in the plugin NY_MicroservicePlugIn) for registering and unregistering the microservice. The dependencies are given to us in the constructor in the form of the IDependencies interface.

    interface Dependencies
{
    @NotNull INamespaceFactory getNamespaceFactory();

    @NotNull IHelperForLocalMicroServices getHelperForLocalMicroServices();
}

Our target is started by a package initializer as usual, see Package Initialization.

public class CPackageExampleMicroservice implements IServiceStarter, IDependencies
{
    private IService mService = null;
    private INamespaceFactory mNamespaceFactory;
    private IHelperForLocalMicroServices mHelperForLocalMicroServices;

    @Override
    public void getDependencies(@NotNull final IServiceDependencyList aDependencyList)
    {
        aDependencyList.add(INamespaceFactory.class);
        aDependencyList.add(IHelperForLocalMicroServices.class);
    }

    @Override
    public void start(@NotNull final IServiceRegistry aServiceRegistry) throws Exception
    {
        if (mService == null)
        {
            mNamespaceFactory = aServiceRegistry.getServiceOrThrow(INamespaceFactory.class);
            mHelperForLocalMicroServices = aServiceRegistry.getServiceOrThrow(IHelperForLocalMicroServices.class);

            mService = new CMyMicroserviceTarget(this);
            mService.activate(aServiceRegistry);
        }
    }

    @Override
    public void stop(@NotNull final IServiceRegistry aServiceRegistry) throws Exception
    {
        if (mService != null)
        {
            mService.deactivate(aServiceRegistry);
            mService = null;
        }
    }

    @Override
    public @NotNull INamespaceFactory getNamespaceFactory()
    {
        return mNamespaceFactory;
    }

    @Override
    public @NotNull IHelperForLocalMicroServices getHelperForLocalMicroServices()
    {
        return mHelperForLocalMicroServices;
    }
}

Of course, you can also register a microservice with the microservice registry via message. It is important to note that when loading our plugin (when the node is booted) the microservice registry is not yet known. Microservice registries are announced to the new node via broadcast message. The broadcast probably arrives after our plugin has been loaded. You can of course register on the broadcast as an observer, which means that you receive the message directly with registration of the microservice registry. But the way shown is the easiest.