Package initialization

Package, van Gogh

Packages are used in Java to group all classes belonging to a module in one place. Only the interface to the module should be public, all other classes and methods remain private.

In many cases, a module has dependencies on other modules or services. Now you could check before each use if all dependencies are fulfilled, i.e. the corresponding modules are available. However, this is a laborious approach.

Automatic dependency injection, such as in Spring Boot, would be a good start. But this is not an option in a dynamic system like nyssr.net . Modules and services are loaded at runtime and can be dropped at any time.

Our answer here is to use service or package starters. In those we define a list of dependencies. The system checks whether our dependencies are met or not every time the service environment is changed. If they are fulfilled, but our package is not started yet, the package will be started. If they are not fulfilled, but our package is already started, it will be stopped.

Package starters are often the only classes that are public in a package. The actual implementation of the functions remains hidden. APIs of the packages are published as interfaces in the service registry. This ensures that no one communicates bypassing the APIs.

Dependencies in unit tests are resolved by calling the start method of the corresponding package starter.

Getting started

We want to create a target, which must be registered in a namespace. To get the namespace, we need the namespace registry. To do this, we create a package private interface in a new package that defines our dependencies. That might be a little overkill, but it's the principle.

interface IDependencies
{
    INamespaceRegistry getNamespaceRegistry();
}

We now define a package starter that implements the interface IServiceStarter as well as our interface IDependencies.

public final class CPackageImplPingTarget implements IServiceStarter, IDependencies
{
    private IService mService;
    private INamespaceRegistry mNamespaceRegistry;

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

    @Override
    public void start(@NotNull final IServiceRegistry aServiceRegistry) throws Exception
    {
        if (mService == null)
        {
            mNamespaceRegistry = aServiceRegistry.getServiceOrThrow(INamespaceRegistry.class);

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

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

    @Override
    public INamespaceRegistry getNamespaceRegistry()
    {
        return mNamespaceRegistry;
    }
}

You can now easily see the three methods of the IServiceStarter as well as the one method of the interface IDependencies.

The only dependency entered in the list is the interface INamespaceRegistry. Additional dependencies may be added as development progresses. These would then have to be entered in the dependency list as well as in the interface IDependencies. The IDependencies interface is passed to all classes in the package so that the dependencies are available everywhere.

If our dependency is fulfilled, here the service INamespaceRegistry is available, the system calls the start() method. We can now fetch the required services. Then we create our required classes, here the target CPingTarget.

CPingTarget implements the IService interface. This is not mandatory, but it simplifies our setup, since this interface consists of an activate() and deactivate() method. We can conveniently call these in the start() and stop() handlers.

Our target is still missing.

The target

final class CPingTarget extends CTarget implements IService
{
    private final IDependencies mDependencies;

    /**
     * Constructor.
     */
    public CPingTarget(@NotNull final IDependencies aDependencies)
    {
        mDependencies = aDependencies;

        addMessageHandler(CRecordPing.ID,
                          this::asyncPing);

    }

    @Override
    public void activate(@NotNull final IServiceRegistry aServiceRegistry) throws Exception
    {
        final INamespace namespace = mDependencies.getNamespaceRegistry()
                                                  .getNamespace(CWellKnownNID.SYSTEM);
        namespace.getTargetRegistry()
                 .registerTarget(this);
    }

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

    private boolean asyncPing(@NotNull final CEnvelope aEnvelope,
                              @NotNull final CRecord aRecord) throws CException
    {
        if (aEnvelope.isAnswer())
        {
            return false;
        }
        else
        {
            final long loop = CRecordPing.getLoop(aRecord,
                                                  0);
            CRecordPing.setLoopMirror(aRecord,
                                      loop);
            aEnvelope.setResultSuccess();
            return true;
        }
    }
}

The activate() and deactivate() methods can be seen very nicely here.

In the activate method, the namespace "SYSTEM" is fetched via the namespace registry. Our target is registered in its target registry. After that it can send and receive messages.

In the deactivate method our target is de-registered.

Consuming a message

By the way, you can also see here how a target consumes a message.

A message handler is registered in the constructor (asyncPing). We have made it a practice to always start message processing methods with async.... . The asyncPing method here only responds to requests, responses are discarded. We get an argument of the message from the record (getLoop) and copy it to another slot (setLoopMirror). As a result, we set the value Success.

Register the service starter

The service starter must still be made known to the system so that it is also started. This is usually done in the class that implements the plugin interface and thus initializes all services in a plugin JAR.

public final class CModuleMyPlugin implements IPlugIn
{
    @Override
    public void startPlugin(@NotNull final URL aUrl,
                            @NotNull final ClassLoader aClassLoader,
                            @NotNull final IServiceRegistry aServiceRegistry)
    {
        aServiceRegistry.addStarter(new CPackageImplPingTarget());
        // Initialize more packages here
    }
}

That's about it. The whole thing is relatively meaningless, since targets implement similar things via the base CTarget class. But it shows how a package is organized, how a target is registered and deregistered, and how a message is received.

nyssr.net - Innovative Distributed System