Getting Started

Introduction

To start using Zenaton, you have to complete 4 steps:

Then you will be able to start writing workflows and tasks as code. All functions are introduced in the how it works page.

If you are starting from scratch, our tutorial is a good candidate, you will be guided through an interactive tour of all the features starting from the setup of your environments to running some example workflows and viewing the executions on your dashboard.

Then you can get some more concrete examples on our github repo, it's usually useful to bootstrap your project.

When you are ready to go live, here are some guides to put your Zenaton workflows in production, you can follow these steps in the documentation according to your preferred programming language

When you are ready to go live, check out our guides for deploying your Zenaton workflows to AWS ,Google Cloud ,Heroku ,Docker and more depending on your preferred programming language

Hello World

We’re going to walk you through creating your first project on your local directory - or in your development environment.

If you are just looking for the easiest way to install and run some workflows to see how Zenton works, we recommend doing our Tutorial.

Here are the steps we will walk through:

  1. Create a directory on our computer or wherever you wish.
  2. Write a couple of example tasks and a workflow so we can get used to the Zenaton syntax.
  3. Run our tasks and workflow locally.
  4. Install the Zenaton Agent and dispatch our tasks and workflows as background processes using the Zenaton engine
  5. View our executed tasks and workflow on the Zenaton Dashboard.

Writing our Tasks and Workflow

First we will create a directory zenaton-test and a src repo inside of it.

mkdir -p zenaton-test/src && cd zenaton-test/src

Now using the Zenaton library, we’re going to create 3 sample tasks into the src directory:

  • The first one will be called GetName and it will return a string ‘world’
  • The second one will be called GetSentence and use the name as the parameter and return ‘hello name’
  • The third one will be called SaySentence and will display a sentence in the console

get_name.rb

<?php

namespace App;

use Zenaton\Interfaces\TaskInterface;
use Zenaton\Traits\Zenatonable;

class GetName implements TaskInterface
{
    use Zenatonable;

    public function handle()
    {
        sleep(3); // simulate a real task

        return "World";
    }
}
const { Task } = require("zenaton");

module.exports = Task('GetName', async function() {
  // simulate a real task
  await new Promise(resolve => setTimeout(resolve, 3000)); 

  return "World";
});
module.exports = async function() {
  // simulate a real task
  await new Promise(resolve => setTimeout(resolve, 3000)); 

  return "World";
};
require 'zenaton'

class GetName < Zenaton::Interfaces::Task
  include Zenaton::Traits::Zenatonable

  def handle
    sleep 3 # simulate a real task

    "World"
  end
end
import time    
from zenaton.abstracts.task import Task
from zenaton.traits.zenatonable import Zenatonable

class GetName(Task, Zenatonable):

    def handle(self):
        time.sleep(3) # simulate a real task

        return "World"

get_sentence.rb

<?php

namespace App;

use Zenaton\Interfaces\TaskInterface;
use Zenaton\Traits\Zenatonable;

class GetSentence implements TaskInterface
{
    use Zenatonable;

    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function handle()
    {
        sleep(1); // simulate a real task

        return "Hello " . $this->name . "!";
    }
}
const { Task } = require("zenaton");

module.exports = Task('GetSentence', {
  async init(name) {
    this.name = name;
  },
  async handle() {
    // simulate a real task
    await new Promise(resolve => setTimeout(resolve, 1000)); 

    return 'Hello ' + this.name + '!';
  }
});
module.exports = async function(name) {
    // simulate a real task
    await new Promise(resolve => setTimeout(resolve, 1000)); 

    return 'Hello ' + name + '!';
};
require 'zenaton'

class GetSentence < Zenaton::Interfaces::Task
  include Zenaton::Traits::Zenatonable

  def initialize(name)
    @name = name
  end

  def handle
    sleep 1 # simulate a real task

    "Hello #{@name}!"
  end
end
import time
from zenaton.abstracts.task import Task
from zenaton.traits.zenatonable import Zenatonable

class GetSentence(Task, Zenatonable):

    def __init__(self, name_):
        self.name_ = name_

    def handle(self):
        time.sleep(1) # simulate a real task

        return "Hello " + self.name_ + '!'

say_sentence.rb

<?php

namespace App;

use Zenaton\Interfaces\TaskInterface;
use Zenaton\Traits\Zenatonable;

class SaySentence implements TaskInterface
{
    use Zenatonable;

    protected $sentence;

    public function __construct($sentence)
    {
        $this->sentence = $sentence;
    }

    public function handle()
    {
        sleep(4); // simulate a real task

        echo($this->sentence . PHP_EOL);
    }
}
const { Task } = require("zenaton");

module.exports = Task('SaySentence', {
  async init(sentence) {
      this.sentence = sentence;
  },
  async handle() {
    // simulate a real task
    await new Promise(resolve => setTimeout(resolve, 4000));

    console.log(this.sentence);
  }
});
module.exports = async function(sentence) {
    // simulate a real task
    await new Promise(resolve => setTimeout(resolve, 4000));

    console.log(sentence);
};
require 'zenaton'

class SaySentence < Zenaton::Interfaces::Task
  include Zenaton::Traits::Zenatonable

  def initialize(sentence)
    @sentence = sentence
  end

  def handle
    sleep 4 # simulate a real task

    puts "#{@sentence}"
  end
end
import time    
from zenaton.abstracts.task import Task
from zenaton.traits.zenatonable import Zenatonable

class SaySentence(Task, Zenatonable):

    def __init__(self, sentence_):
        self.sentence_ = sentence_
    
    def handle(self):
        time.sleep(4) # simulate a real task

        print(self.sentence_)

        return

Now, lets write a workflow orchestrating our three tasks. Our workflow will

  • dispatch GetName, to retrieve a string "World"
  • using previous result, it will dispatch GetSentence, to retrieve the string "Hello World!"
  • using previous result, it will dispatch SaySentence, to print it on the console.

hello_world.rb

<?php

namespace App;

use Zenaton\Interfaces\WorkflowInterface;
use Zenaton\Traits\Zenatonable;

class HelloWorld implements WorkflowInterface
{
    use Zenatonable;

    public function handle()
    {
        $name = (new GetName())->execute();

        $sentence = (new GetSentence($name))->execute();

        (new SaySentence($sentence))->execute();
    }
}
const { Workflow } = require('zenaton');
const GetName = require('./GetName');
const GetSentence = require('./GetSentence');
const SaySentence = require('./SaySentence');

module.exports = Workflow("HelloWorld", async function() {
    const name = await new GetName().execute();

    const sentence = await new GetSentence(name).execute();

    await new SaySentence(sentence).execute();
});
module.exports = function*() {
    const name = yield this.run.task("GetName");
    
    const sentence = yield this.run.task("GetSentence", name);
    
    yield this.run.task("SaySentence", sentence);
};
require './src/get_name'
require './src/get_sentence'
require './src/say_sentence'

# :nodoc:
class HelloWorld < Zenaton::Interfaces::Workflow
  include Zenaton::Traits::Zenatonable

  def handle
    name = GetName.new.execute

    sentence = GetSentence.new(name).execute

    SaySentence.new(sentence).execute
  end
end
from src.get_name import GetName
from src.get_sentence import GetSentence
from src.say_sentence import SaySentence
from zenaton.abstracts.workflow import Workflow
from zenaton.traits.zenatonable import Zenatonable

class HelloWorld(Workflow, Zenatonable):

    def handle(self):
        name = GetName().execute()

        sentence = GetSentence(name).execute()

        SaySentence(sentence).execute()
If you want to do more with your workflows learn more about implementing workflows with Zenaton.

Now, in zenaton-test directory, add a Gemfile file with our library as a dependency:

Gemfile

source 'https://rubygems.org'

gem 'zenaton'

Then, still in zenaton-test directory, install dependencies:

bundle install

If we wanted to run a quick test of our workflow locally, we would just run our HelloWorld workflow by calling the handle method directly! To do this, we will create the following file:

launch_local.rb

<?php

// load dependencies
require __DIR__.'/vendor/autoload.php';

// processing a workflow in foreground
(new App\HelloWorld())->handle();
// load dependencies
const HelloWorld = require("./src/HelloWorld");

// processing a workflow in foreground
new HelloWorld().handle();
# load dependencies
require 'bundler/setup'
require './src/hello_world'

# processing a workflow in foreground
HelloWorld.new.handle
# load dependencies
import zenaton.client
from src.hello_world import HelloWorld

# init Zenaton client
zenaton.client.Client(
    'AppId', 
    'ApiToken',
    'dev'
)

# processing a workflow in foreground
HelloWorld().handle()

And now we can run this file with following command:

php launch_local.php
node launch_local.js
ruby launch_local.rb
python launch_local.py

So far we’ve create 3 tasks and a workflow to orchestrate and run those three tasks locally.

Processing our Tasks in the Background

Now we are going to install the Zenaton Agent on our computer so that we can let Zenaton orchestrate our tasks and workflow in the background and dispatch them to our computer - via the Agent.

The beauty of Zenaton is that all of this happens without having to write any additional code. You just have to install our Agent and deploy your sources wherever you want your tasks to be processed. When you are working locally, your sources are already in your computer, so we only need to install the Agent locally.

If we wanted to deploy to another environment (like in production), we would need to install the Zenaton Agent on the servers ("workers") where we wanted to execute tasks and (in client mode) on the servers from where tasks and workflows are dispatched. Zenaton automatically orchestrates the processing of our tasks and workflows on distributed workers and monitors all of the activity.

If you have not already installed the Zenaton Agent on your computer, we will do that here.

curl https://install.zenaton.com | sh
Depending on your configuration, you may be asked for a password to allow global installation of Zenaton.

If you need to deploy the Agent on a particular system like Docker, Heroku, or Clever Cloud, we have some precooked solutions for you.

Since the Agent will process our tasks and workflows in the background, we will need to provide a boot file that will be loaded before any processing:

boot.rb

<?php

// load dependencies
require __DIR__.'/vendor/autoload.php';
// load dependencies
const HelloWorld = require("./src/HelloWorld");
// load dependencies
const { task, workflow } = require('zenaton');

// load definitions
const helloWorld = require("./src/HelloWorld");
const getName = require("./src/GetName");
const getSentence = require("./src/GetSentence");
const saySentence = require("./src/SaySentence");

// apply definitions
workflow("HelloWorld", helloWorld);
task("GetName", getName);
task("GetSentence", getSentence);
task("SaySentence", saySentence);
# load dependencies
require 'bundler/setup'
require './src/hello_world'
# load dependencies
import zenaton.client
from src.hello_world import HelloWorld 

zenaton.client.Client(
    'AppId',
    'ApiToken',
    'dev'
)

Finally, we need to provide our AppId and ApiToken which we will find on Zenaton API section, so that the Agent can listen to our configuration and report processing status back to our engine and your Zenaton dashboard:

zenaton listen --boot=boot.php --app_id=AppId --api_token=ApiToken --app_env=dev
zenaton listen --boot=boot.js --app_id=AppId --api_token=ApiToken --app_env=dev
zenaton listen --boot=boot.js --app_id=AppId --api_token=ApiToken --app_env=dev
zenaton listen --boot=boot.rb --app_id=AppId --api_token=ApiToken --app_env=dev
zenaton listen --boot=boot.py --app_id=AppId --api_token=ApiToken --app_env=dev

You should see Zenaton worker is now listening to app "AppId" on env "dev".

Note that once an Agent is installed, we can always check the Agents monitoring section on the dashboard to view Agent settings and worker activity for each environment.

Ok we are now ready to run! Let's make sure that the Zenaton library is initialized with our credentials before dispatching them, eg. we will create the following file:

launch.rb

<?php

// load dependencies
require __DIR__.'/vendor/autoload.php';

// init Zenaton client
Zenaton\Client::init(
    'AppId',
    'ApiToken',
    'dev'
);

// dispatching a task to be processed in background
(new App\GetName())->dispatch();

// dispatching a workflow to be processed in background
(new App\HelloWorld())->dispatch();

// load dependencies
const GetName = require("./src/GetName");
const HelloWorld = require("./src/HelloWorld");
const { Client } = require("zenaton");

// init Zenaton client
Client.init(
    'AppId', 
    'ApiToken',
    'dev'
);

// dispatching a task to be processed in background
new GetName().dispatch();

// dispatching a workflow to be processed in background
new HelloWorld().dispatch();
// load dependencies
const { Client } = require("zenaton")
const client = new Client(AppId, ApiToken, "dev");

// dispatching a task to be processed in background
client.run.task("GetName");

// dispatching a workflow to be processed in background
client.run.workflow("HelloWorld")
# load dependencies
require 'bundler/setup'
require './src/hello_world'
require './src/get_name'

# init Zenaton client
Zenaton::Client.init(
    'AppId', 
    'ApiToken',
    'dev'
)

# dispatching a task to be processed in background
GetName.new.dispatch

# dispatching a workflow to be processed in background
HelloWorld.new.dispatch
# load dependencies
import zenaton.client
from src.get_name import GetName 
from src.hello_world import HelloWorld 

# init Zenaton client
zenaton.client.Client(
    'AppId', 
    'ApiToken',
    'dev'
)

# dispatching a task to be processed in background
GetName().dispatch()

# dispatching a workflow to be processed in background
HelloWorld().dispatch()

And now we can run this file with following command:

php launch.php
node launch.js
node launch.js
ruby launch.rb
python launch.py

Logging

Everything written to stdout during the processing in background of your your tasks will be in the zenaton.out file, that you'll find in the same directory than our sources. For example, after first launch, you should see:

zenaton.out

Hello World!

If we do not get the expected result, in stderr during the processing of our tasks will be in the zenaton.err file, that you'll find in the same directory than our sources.

Monitoring

We can check the Agents section on our dashboard which allows us to monitor the real-time activity of each of our connected Agents:

We can check the Tasks section, of the dashboard to monitor the real-time processing of our individual tasks:

And we can also check the Workflows section of our dashboard to see an overview of all of our processed tasks and workflows, including real time-monitoring of tasks that are in progress.

And we only need to click on each task to see the full details:

Tutorial

The easiest way to get started using Zenaton is to do our tutorial.

The tutorial will walk you through installing the Zenaton Agent on your computer, downloading our library for your preferred programming language, and running all of the examples from our examples repo.

This will give you an opportunity to see what our syntax looks like for workflows. You will also see how quickly you can set up your environment and start processing background tasks using the Zenaton service.

You will also have the opportunity to view all of your executed tasks and workflows displayed on your Zenaton dashboard.

In order to run the tutorial you need to have an active Zenaton account to access your unique Application Id and Api Token.

Examples

Download our examples repo wherever you want (on the same machine where you already launched a Zenaton Agent). (If you have completed the tutorial, you would have already downloaded the examples repository.)

git clone https://github.com/zenaton/examples-php.git
git clone https://github.com/zenaton/examples-node.git
git clone https://github.com/zenaton/examples-node.git
git clone https://github.com/zenaton/examples-ruby.git
git clone https://github.com/zenaton/examples-python.git

Then, install all dependencies

composer install
npm install
npm install
bundle install
pip3 install -r requirements.txt

Then, add a .env file

cp .env.example .env

And update the .env file with your application id and your api token found here. Now that your .env file is complete, you can launch Zenaton Agent with these credentials:

zenaton listen --env=.env --boot=src/bootstrap.php
zenaton listen --env=.env --boot=boot.js
zenaton listen --env=.env --boot=boot.js
zenaton listen --env=.env --boot=boot.rb
zenaton listen --env=.env --boot=boot.py
zenaton listen --env=.env --boot=boot/boot.go

Note: .env is the default value for --env option. You can also omit it here.

A Zenaton Agent is natively able to handle concurrent tasks. There is no need to launch it more than once on the same server. Your credentials will be used to listen to any task sent by your application to this Agent, within the provided environment.

An environment is a way to isolate workflow instances between production and development. A workflow is always launched within a given environment - and its tasks are handled only by Agents listening to this environment.

Now you can look at all provided examples - a launcher is provided for each of them, eg. launch_wait.rb

<?php
require __DIR__.'/autoload.php';

(new WaitWorkflow())->dispatch();
// Get the configured Zenaton client
require("./client");

// Start a new instance of the workflow
const WaitWorkflow = require("./Workflows/WaitWorkflow");
new WaitWorkflow().dispatch();
// Get the configured Zenaton client
const run = require("./client").run;

// Start a new instance of the workflow
run.workflow("WaitWorkflow")
# Get the configured Zenaton client
require './client'

# Start a new instance of the workflow
require './workflows/wait_workflow'
WaitWorkflow.new.dispatch
# Get the configured Zenaton client
import .client

# Start a new instance of the workflow
from .workflows.wait_workflow import WaitWorkflow
WaitWorkflow().dispatch()
package main

import (
    _ "github.com/zenaton/excamples-go" // Initializes Zenaton client with credentials
    "github.com/zenaton/excamples-go/workflows"
)

// Start a new instance of the workflow
workflows.WaitWorkflow().Dispatch()

such that

php bin/launch_wait.php
node launch_wait.js
node launch_wait.js
ruby launch_wait.rb
python3 launch_wait.py
go run wait/main.go

will execute this WaitWorkflow workflow:

<?php

use Zenaton\Interfaces\WorkflowInterface;
use Zenaton\Tasks\Wait;
use Zenaton\Traits\Zenatonable;

class WaitWorkflow implements WorkflowInterface
{
    use Zenatonable;

    public function handle()
    {
        (new TaskA())->execute();
        (new Wait())->seconds(5)->execute();
        (new TaskB())->execute();
    }
}
const { Workflow, Wait } = require("zenaton");
const TaskA = require("../Tasks/TaskA");
const TaskB = require("../Tasks/TaskB");

module.exports = Workflow("WaitWorkflow", async function() {
    await new TaskA().execute();

    await new Wait().seconds(5).execute();

    await new TaskB().execute();
});
const { workflow } = require("zenaton");

module.exports = workflow("WaitWorkflow", function*() {
    yield this.run.task("TaskA");

    yield this.wait.for(duration.seconds(5));

    yield this.run.task("TaskB");
});
require './tasks/task_a'
require './tasks/task_b'

class WaitWorkflow < Zenaton::Interfaces::Workflow
    include Zenaton::Traits::Zenatonable

    def handle
        TaskA.new.execute
        Zenaton::Tasks::Wait.new.seconds(5).execute
        TaskB.new.execute
    end
end

from zenaton.abstracts.workflow import Workflow
from zenaton.traits.zenatonable import Zenatonable
from zenaton.tasks.wait import Wait

from tasks.task_a import TaskA
from tasks.task_b import TaskB


class WaitWorkflow(Workflow, Zenatonable):

    def handle(self):
        TaskA().execute()
        Wait().seconds(5).execute()
        TaskB().execute()
package workflows

import (
    "github.com/zenaton/examples-go/tasks"
    "github.com/zenaton/zenaton-go/v1/zenaton/task"
    "github.com/zenaton/zenaton-go/v1/zenaton/workflow"
)

var WaitWorkflow = workflow.New("WaitWorkflow",
    func() (interface{}, error) {

        tasks.A.New().Execute()

        task.Wait().Seconds(5).Execute()

        tasks.B.New().Execute()
        return nil, nil
    })

These examples include:

  • launch_sequential illustrates a sequential execution of tasks and how you can use a task result in a following task;
  • launch_parallel illustrates some parallel executions of tasks and how the workflow waits for all results before going further;
  • launch_asynchronous illustrates some asynchronous "fire and forget" executions;
  • launch_event illustrates how you can retrieve a workflow instance, send an event to it, and handle this event to modify workflow execution;
  • launch_wait illustrates how you can use the provided Waitclass to manage time in your workflow;
  • launch_wait_event illustrates how you can use the provided WaitEventclass to wait for an external event before going further (with a timeout);

Feel free to play with these examples, eg.

  • launch multiple workflows in parallel,
  • run and modify each workflow,
  • run multiple Zenaton Agents on different machines (installed and configured as described above).
Last but not least, you can add an intentional error in these examples to see how the workflow stops and how you can see the error here. Fix your code and click 'retry' to resume the workflow execution without any additional effort!