Overview

Why Zenaton?

We built Zenaton to give one developer the power and control of an entire team of software developers to manage sophisticated business and data processes.

Software is getting more modular and building good software requires passing data and triggering events between internal services and external SaaS services, such as email delivery, communications, payment, delivery, and more.

These SaaS services allow us to build software quickly, prototype, iterate, and reduce our development cycle. Many of these services offer out of the box integrations which in theory allow us to 'plug' them into our product or into each other.

However, integrating multiple services is complicated and there are many hidden limitations - especially when some of them are asynchronous and integration requires many different tasks based on different event triggers. This requires orchestration or building a workflow.

The orchestration of services and business logic is the core of the product we are building. Because this orchestration is so crucial to your product, it is important to have full control over the logic and easily understand and make updates. So…Zenaton has created a new way to orchestrate these tasks that can be done just using code (!)

Zenaton offers a simple syntax that is easy to learn and can be combined to create infinite possibilities. When you consider the dashboard monitoring and error handling capabilities, it is the equivalent of having super powers. With Zenaton, you can write the business logic for your tasks or workflows directly into your code in an easily readable format so that anyone on the team can understand the logic, troubleshoot and make changes.

We have made it easy to manage all of your background processes without having to worry about... well, everything.

  • Real-time monitoring of all of your background tasks and workflows on the Zenaton dashboard
  • Add new workers by installing the Zenaton Agent on your servers
  • Get notified when tasks fail and view or retry errors on the Zenaton dashboard
  • Read and change business logic through your code
  • Take advantage of our customer support! Just reach out.

Here is how it works:

  • when your code dispatches a task or a workflow, our library sends a request to a listening Agent
  • the Agent forwards this request to our Engine, which will orchestrate and monitor its processing
  • when a task should be processed immediately, our Engine sends a request to listening Agents (through queues that are automaticaly deployed and hosted for you)
  • the Agent receiving this message triggers the processing of the requested task, and sends back the result to our Engine
  • in case of a workflow, or if an error occurs, the Engine will handle the situation accordingly and send the metrics back to your dashboard.

Zenaton why

Single Task vs. Workflows

When running background processes with Zenaton you can either write a single task or a write a sequence of related tasks within a workflow.

Single Task

Tasks are the basic building blocks of Zenaton. Tasks are where you code perform your actual work and there is no limitation on what you can do there. Processing of tasks is offloaded onto one of your servers (called a worker then), but usually different from the one from which they were dispatched.

A few examples of tasks:

  • Transform an image
  • Extract, Transform or Load data
  • Send a slack notification
  • Send an email

Using Zenaton library, writing a task is as simple as

<?php

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

class MyTask implements TaskInterface
{
    use Zenatonable;

    public function __construct(...)
    {
        // initialize properties
    }

    public function handle()
    {
        // task implementation
    }
}
const { Task } = require("zenaton");

module.exports = Task("MyTask", {
  init(...) {
    // initialize properties
  },
  async handle() {
    // task implementation
  }
});
const { task } = require("zenaton");

module.exports = task("MyTask", async function(...input) {
  // task implementation
});
require 'zenaton'

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

  def initialize(...)
    # initialize properties
  end

  def handle
    # task implementation
  end
end
from zenaton.abstracts.task import Task
from zenaton.traits.zenatonable import Zenatonable

class MyTask(Task, Zenatonable):
    def __init__(self, ...):
        # initialize properties
        
    def handle(self):
        # Your task implementation

Once Zenaton configured, dispatching a task to be processed on one of your workers is as easy as

(new MyTask())->dispatch();
await new MyTask().dispatch();
const { Client } = require("zenaton");
const client = new Client(app_id, api_token, app_env);

client.run.task("MyTask");
MyTask.new.dispatch
MyTask().dispatch()

Workflows

Workflows are a set of tasks intended to be processed in a specific order, despite the fact that each of them can have been processed on different machines. Things you can do with a workflow:

  • Handle errors in payment process
  • Operate data pipelines with multiple steps
  • Orchestrate an order from online request to delivery

Implementing worklows implies dealing with (notoriously difficult) distributed systems, where you have to deal with:

  • Task failures
  • Network failures
  • Lack of monitoring or alerting tools
  • Difficulty of having a global understanding of a workflow state
  • Complexity of updating workflow's logic
  • Managing intermediary systems such as crons or states in database

With Zenaton, writing such sequences is very easy. Whatever the complexity of your case, we handle all of the complexity for you:

  • Resuming a failed tasks is easy, it also resumes the workflow from where it failed
  • Automatically recovering from network issues
  • Real-time monitoring of your tasks and workflow
  • Real-time monitoring of infrastructure
  • Modifying your workflows is easy, even if some instances are still running
  • Coding your workflows is easy, even for time-sensitive or event-driven workflows

Using Zenaton library, writing a workflow is as simple as

<?php
    
use Zenaton\Interfaces\WorkflowInterface;
use Zenaton\Traits\Zenatonable;

class MyWorkflow implements WorkflowInterface
{
    use Zenatonable;

    public function __construct(...)
    {
        // initialize properties
    }

    public function handle()
    {
        // workflow implementation
    }
}
const { Workflow } = require("zenaton");

module.exports = Workflow("MyWorkflow", {
  init(...) {
    // initialize properties
  },
  async handle() {
    // workflow implementation
  }
});
const { workflow } = require("zenaton");

module.exports = workflow("MyWorkflow", function*() {
    // workflow implementation
});
require 'zenaton'

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

  def initialize(...)
    # initialize properties
  end

  def handle
    # workflow implementation
  end
end
from zenaton.abstracts.workflow import Workflow
from zenaton.traits.zenatonable import Zenatonable

class MyWorkflow(Workflow, Zenatonable):
    def __init__(self, ...):
        # initialize properties
        
    def handle(self):
        # Your workflow implementation

Once Zenaton configured, dispatching a workflow to be processed on your workers is as easy as

(new MyWorkflow(...))->dispatch();
await new MyWorkflow(...).dispatch();
const { Client } = require("zenaton");
const client = new Client(app_id, api_token, app_env);

client.run.workflow("MyWorkflow");
MyWorkflow.new(...).dispatch
MyWorkflow(...).dispatch()

With Zenaton, implementing a workflow is very easy as we use our prefered language. For example:

<?php

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

class SequentialWorkflow implements WorkflowInterface
{
    use Zenatonable;

    public function handle()
    {
        $a = (new TaskA())->execute();

        if (0 < $a) {
            (new TaskB())->execute();
        } else {
            (new TaskC())->execute();
        }

        (new TaskD())->execute();
    }
}
const { Workflow } = require("zenaton");
const TaskA = require("../Tasks/TaskA");
const TaskB = require("../Tasks/TaskB");
const TaskC = require("../Tasks/TaskC");
const TaskD = require("../Tasks/TaskD");

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

  if (0 < a) {
    await new TaskB().execute();
  } else {
    await new TaskC().execute();
  }

  await new TaskD().execute();
});
"use strict";
const { workflow } = require("zenaton");

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

  if (0 < a) {
    yield this.run.task("TaskB");
  } else {
    yield this.run.task("TaskC");
  }
  yield this.run.task("TaskD");
});
require './tasks/task_a'
require './tasks/task_b'
require './tasks/task_c'
require './tasks/task_d'

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

  def handle
    a = TaskA.new.execute

    if a > 0
      TaskB.new.execute
    else
      TaskC.new.execute
    end

    TaskD.new.execute
  end
end
from tasks.task_a import TaskA
from tasks.task_b import TaskB
from tasks.task_c import TaskC
from tasks.task_d import TaskD

from zenaton.abstracts.workflow import Workflow
from zenaton.traits.zenatonable import Zenatonable


class SequentialWorkflow(Workflow, Zenatonable):

    def handle(self):

        a = TaskA().execute()

        if a > 0:
            TaskB().execute()
        else:
            TaskC().execute()

        TaskD().execute()