1.0.1-alpha.9 • Published 2 years ago

@darlean/daemon-rha v1.0.1-alpha.9

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
2 years ago

Darlean Daemon with integrated Runtime and Helpers

Daemon for running a Darlean Runtime node with integrated helpers. This package can also be used for custom NodeJS apps.

IMPORTANT: Darlean is still in alpha. Functionality may change in backwards-incompatible ways. Use at your own risk! We are working hard to reach beta and stable!

Starting the runtime

From a local git clone

When you have a local git clone of this package, you can start a runtime node by using

npm run serve node00@cluster0

The node00@cluster0 is the name of the node. It must correspond to any of the node names in conf/settings.json. Other configured names are node01@cluster0 and node02@cluster0.

To start all 3 runtime nodes at once (and also stop them alltogether), use

npm run serve:cluster

To stop the applications, press control-c (at least, on windows). Wait a while until the application can nicely stop, and then press control-c again.

The nodes will use the configuration file that is present in the config folder.

Using NPX

When you do not have a local git clone of this package, just use npx to automatically download, install and run the 3 daemon instances.

Before you do,copy the settings file config/default.json to runtime/config/default.json relative to your current working directory.

Then, start the first Darlean node:

$ cd runtime
$ npx @darlean/daemon-rha node00@cluster0

Don't worry about the many log messages; they indicate that the node is trying to look for other nodes to form a majority. Once at least one other node is running, the log flood will stop.

So, let's start the other nodes new command prompts:

$ npx @darlean/daemon-rha node01@cluster0

and

$ npx @darlean/daemon-rha node02@cluster0

By now, the log flood should have stopped, and the cluster should be up.

Ensure that the runtime is running

Navigate to http://localhost:7000, http://localhost:7001 or http://localhost:7002 to view the webbased control panel for node00, node01 and node03, respectively. It does not matter which one you use, they should look and behave identical. The port numbers are simply 1000 more than the port numbers on which the hosts are listening (6000, 6001 and 6002, by default).

Creating your own app

Create a new typescript project:

$ mkdir test
$ cd test
$ npm i typescript --save-dev
$ npx tsc --init

Adjust the test/tsconfig.conf file so that the compiled files are placed in a separate folder (instead of polluting the src folder) and that experimental decorators are enabled:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "strict": true,
    "outDir": "dist",
    "sourceMap": true,
    "experimentalDecorators": true
  }
}

Then install the darlean packages:

$ cd test
$ npm install @darlean/daemon-rha
$ npm install @darlean/app-lib

Now, create a test/src/index.ts file with the following contents:

import * as daemon from '@darlean/daemon-rha';
import * as darlean from '@darlean/app-lib';

@darlean.actor({ name: 'CalculatorActor' })
export class CalculatorActor extends darlean.BaseActor {
    private value = 0;

    public async activate(context: darlean.IActionContext): Promise<void> {
        const state = context.getService('state') as darlean.persistence.IPersistenceService;
        const data = await state.load({
            lookupKeys: ['value']
        });
        if (data) {
            this.value = darlean.persistence.fromJson(data) as number;
        } else {
            this.value = 1;
        }
    }

    @darlean.action({ name: 'Add', description: 'Adds a value to the last calculated value' })
    public async add(value: number, context: darlean.IActionContext): Promise<number> {
        this.value += value;
        await this.persist(context);
        return this.value;
    }

    @darlean.action({
        name: 'Multiply',
        description: 'Multiplies our last calculated value with the provided value'
    })
    public async multiply(value: number, context: darlean.IActionContext): Promise<number> {
        this.value *= value;
        await this.persist(context);
        return this.value;
    }

    @darlean.action({
        name: 'Divide',
        description: 'Divides our last calculated value by the provided value'
    })
    public async divide(value: number, context: darlean.IActionContext): Promise<number> {
        this.value /= value;
        await this.persist(context);
        return this.value;
    }

    protected async persist(context: darlean.IActionContext): Promise<void> {
        const state = context.getService('state') as darlean.persistence.IPersistenceService;
        await state.store({
            lookupKeys: ['value'],
            value: darlean.persistence.toJson(this.value)
        });
    }
}

async function main() {
    const fullId = process.argv[2];
    const app = new daemon.AppRha(fullId, 'a');

    const logger = app.getLogger();
    // const settings = app.getSettings();

    app.registerActor(
        darlean.BaseActorFactory(logger, {
            clazz: CalculatorActor,
            capacity: 100
        })
    );

    await app.start();

    const newValue = await app.perform(
        {
            actorType: 'CalculatorActor',
            actorId: ['MyCalculator'],
            actionName: 'Add',
            data: 4
        },
        {
            waitFor: 'performed'
        }
    );
    console.log('Result of calculation', newValue);
}

if (require.main === module) {
    main()
        .then()
        .catch((e) => console.log(e));
}

This file registers a calculator actor that can perform additions, multiplications and divisions on the last calculated value. The last calculated value is stored in memory, persisted after every operation and reloaded when the actor is activated.

Create a settings file test/config/default.json:

{
    "logging": {
        "masks": [
            { "mask": "*", "levels": "INFO_AND_UP"}
        ]
    },
    "runtime": {
        "nodes": {
            "node00@cluster0": {
                "port": "6000"
            },
            "node01@cluster0": {
                "port": "6001"
            },
            "node02@cluster0": {
                "port": "6002"
            }

        }
    }
}

Compile your application (from the test folder):

$ npx tsc

And run it (while the runtime nodes are still running):

$ node dist/index.js test@cluster0

See the result of the calculation. Stop your application and run it again, and be surprised that it ends up with a different value...

To do some debugging:

  • Navigate to http://localhost:7000
  • Check that your calculator actor is in the actor placement directory ("Actor Placement")
  • Inspect the state (last calculated value) that was persisted by the calculator actor ("State").
  • Inspect the logging ("Logging")

Running with only 1 runtime node

Getting tired of starting and stopping all 3 runtime nodes? You can also run Darlean with only 1 runtime node (but you lose the advantages of redundancy). To do so, remove all occurrances of node01@cluster0 and node02@cluster0 from the settings files for both the runtime nodes (runtime/config/default.json) and your test application (test/config/default.json). The only remaining runtime node node00@cluster0 then understands that it is the only runtime node, so it does not wait for other nodes to join but immediately assumes that it has the majority.

Note: Strictly speaken, it is only necessary to remove the other nodes from the settings for the runtime (not for your test application), but it will save you a lot of "cannot connect" warnings when you remove them there as well.

Running without runtime nodes

For those that are really lazy -- it is possible to run Darlean even without a runtime node. That is possible by configuring your test app to include the Runtime and the Helpers by itself.

Step 1: In `src/index.ts, adjust line

const app = new daemon.AppRha(fullId, 'a');

into

const app = new daemon.AppRha(fullId, 'rha');

This instructs your test application to also launch Runtime (r) and Helper (h) functionality.

Step 2: Copy the Runtime settings file runtime/config/default.json (without all references to node01@cluster0 and node02@cluster0) over the existing test/config/default.json file. This is required because the Runtime settings file contains additional configuration regarding the persistence service, which is required for our app when it wants to become a Runtime and Helper node by itself.

Step 3: Stop all other processes, and just start your test application with node id node00@cluster0:

$ node dist/index.js node00@cluster0

Happy Darleaning!

More information

More information: http://darlean.io

TODO: Finish the remainder of this readme

Functionality

Installation

Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.

Usage

Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.

Support

Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.

Roadmap

If you have ideas for releases in the future, it is a good idea to list them in the README.

Contributing

State if you are open to contributions and what your requirements are for accepting them.

For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.

You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.

Authors and acknowledgment

Show your appreciation to those who have contributed to the project.

License

For open source projects, say how it is licensed.

Project status

If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.