Runtimes are common name for both processes and threads created by Kraken. They are abstractions that allows operating on them using single interface. This article describes how runtimes are implemented and how they can be used in your application along with tips and best-practices
Container is an instance of any given runtime that contains both runtime's logic and all of its services. Container is Kraken's implementation of an actor, therefore Kraken Framework as a whole can be defined as actor-based system, but because of chosen naming convention all of documentation pages refers to it as container-based system. Keep in mind, that both of these definitions are in fact different names for the same concept.
The most important parts that composes typical container are state, behaviour, messaging mechanisms, supervision mechanisms and child containers. All of them are encapsulated behind the container reference. Noteworthy aspect about containers is that they have an explicit lifecycle and are not automatically destroyed when no longer referenced. It is your responsibility to make sure that each container will eventually be destroyed. This gives you control over how resources are released when any container is destroyed.
Each container is an isolated part of Kraken architecture, closed inside of its own runtime. It is impossible to reference objects or call functions directly on separate container. To be able to communicate between containers one can use container reference. Container reference is an unique alias that you passed during creation of container alongside its name. This alias can be treated as container identifier.
Containers form hierarchies. One container, which covers some functional need in the application might want to split up its task into smaller, more manageable pieces. For this purpose it starts child containers which it supervises. The details of supervision and the underlying concepts can be found in separate, supervision article.
Container might be in one of five states: created
, destroyed
, started
, stopped
and failed
.
The created
state is being granted to container after it has finished all of its constructing, booting and configuring. created
state might change to either started
or destroyed
.
The destroyed
state is the first and the last state of container lifecycle. It is granted to container either shortly after creation of runtime or just before destructing its runtime. destroyed
state might change only to created
state.
The started
state is the state that occurs when not only Kraken but also your application logic has started. started
state might change to either stopped
or failed
.
The stopped
state is the state in which your application logic should close all publicly opened ports and halt all processing until its started again. It might be compared to maintenance mode in other popular frameworks. The stopped
state might change once again to started
state or to destroyed
one.
The failed
state is special kind of state that is invoked during started
state when unhandled exception is thrown. It stops all execution forcibly, changes loop flow to emergency and notifies local supervision system about problem. The failed
state might change to started
or stopped
state.
Behaviour of a container is the custom application logic that you provided. It combines set of commands, services and cyclic operations. The behaviour is set on the runtime creation, but may change over time as your application architecture reacts to your application needs or container itself changes its state. The initial behavior defined during construction of the container is special in the sense that a restart of the container will reset its behavior to this initial one.
Interaction between isolated containers should follow messaging-pattern, meaning that the only way of interacting between containers should be done via sending and receiving messages. Kraken provides its containers with special channels that performs inter-container communication out of the box. Each container has exactly one channel to which all senders enqueue their messages. Enqueuing happens in the time-order of send operations, which means that messages sent from different containers may not have a defined order at runtime due to the apparent randomness of distributing containers across threads and processes. Sending multiple messages to the same target from the same container, on the other hand, will enqueue them in the same order.
{notice} It is recommended to read more about how messaging is done in documentation's separate messaging article.
Kraken handles all faults transparently, applying one of the strategies described in supervision and monitoring for each incoming failure. There are two layers of supervision system. The first, one called local supervisor allows each of your containers to fix its own problems. If local supervision system is not able to solve the problem that occurred, it is able to delegate the issue to remote supervisor, which then will decide what two do. Local supervisor is defined in the container's own memory while remote supervisor is a supervisor existing in the parent container.
{notice} It is recommended to read more about how Kraken implements supervision system in its's separate supervision article.
Each container is able to create additional containers for delegating sub-tasks, and if it does, it automatically becomes their supervisor. The list of children is maintained within the container's runtimes manager. Modifications to the list are done by creating and destroying children, which happen behind the scenes in an asynchronous way.
This section contains basic patterns for using Kraken containers.
To write a runtime, you have to create a class in Process
or Thread
application namespace. In default configuration the paths are App\Process
and App\Thread
. Created class have to extend Kraken\Runtime\RuntimeContainer
and implement Kraken\Runtime\RuntimeContainerInterface
. After doing that Kraken Framework will have an access for creating this kind of runtimes, but won't construct them without your command to do so.
Your runtime may look like this:
class TestContainer extends RuntimeContainer implements RuntimeContainerInterface
{
protected function config(CoreInterface $core)
{
// this method should return additional configurations for this runtime if needed
// in most cases you won't be using this at all since the main place of configuring
// your application should take place in 'data\config' directory
return [];
}
protected function boot(CoreInterface $core)
{
// this method should contain additional boot logic for this runtime if needed,
// in most cases you won't be using this at all since the main place of booting
// your application should take place in 'data\bootstrap' directory
return $this;
}
protected function construct(CoreInterface $core)
{
// this is the method that will be executed after creating, configuring and booting
// is completed, it is the most important part of runtime and should contain your
// application logic
return $this;
}
}
{warning} Remember that the default pattern for runtimes is "App%type%%name%%name%Container", if you want to create a thread-based runtime under the name of Test its namespace should be "App\Thread\Test\TestContainer". This pattern can be adjusted in
kraken.process
andkraken.thread
files.
To run this runtime you should either code it into default Main
runtime that is provided or dynamically using kraken process:create Main T1 Test
command. T1 here is an aliased for referencing container which is going to be created from outside.
{tip} If you have problem with creating containers, check if channel ports are configured properly.
To access current Kraken container, you can resolve Runtime
service or Kraken\Runtime\RuntimeContainerInterface
interface using service container.
// this
$runtime = $container->make('Runtime');
// or this
$runtime = $container->make(RuntimeContainerInterface::class);
The container interface extends Kraken\Runtime\RuntimeContextInterface
so you are able to use methods such as getType
, getParent
, getAlias
, getName
and getArgs
for getting all information about current container context.
$alias = $runtime->getAlias();
The container's state might be get using following syntax:
$state = $runtime->getState();
All operations on child containers, including creating, destroying, starting and stopping can be done via Kraken\Runtime\RuntimeManagerInterface
. It can be resolved from within current container via:
$manager = $runtime->getManager();
For example, creating a child container might look like this:
$manager = $runtime->getManager();
$manager
->createProcess($containerAlias, $containerName)
->done(function($result) {
echo $result . PHP_EOL;
});
{notice} It is safe to contain
createProcess
calls on runtime's construction, because Kraken checks for runtime existence before trying to create it. If it exists, the function will return success, without creating duplicate.
To execute command on one of child containers, use:
$manager = $runtime->getManager();
$manager
->sendCommand($containerAlias, $commandName, $commandParams)
->done(function($result) {
// $result is command returned value
});
{notice} RuntimeManager provides also
sendMessage
andsendRequest
for using non-command related communication.
Kraken allows you to adapt your application on the fly, without a need to hardcode hierarchy into your application. For, example adding B process-based container implementing App\Process\MyProc\MyProcContainer
class as a child of A container, can be done via console:
$> kraken process:create A B MyProc
Kraken console offers a set of useful commands for adapting and managing your application. You can read more about them in console article.