This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Entities

The parts that OpenServerless applications are made of

Entities

OpenServerless applications are composed by some “entities” that you can manipulate either using a command line interface or programmatically with code.

The command line interface is the ops command line tools, that can be used directly on the command line or automated through scripts. You can also a REST API crafted explicitly for OpenServerless.

The entities available in OpenServerless are:

  • Packages: They serve as a means of grouping actions together, facilitating the sharing of parameters, annotations, etc. Additionally, they offer a base URL that can be utilized by web applications.

  • Actions: These are the fundamental components of a OpenServerless application, capable of being written in any programming language. Actions accept input and produce output, both formatted in JSON.

  • Activations: Each action invocations produces an activation id that can be listed. Action output and results logged and are associated to activations and can be retrieved providing an activativation id.

  • Sequences: Actions can be interconnected, where the output of one action serves as the input for another, effectively forming a sequence.

  • Triggers: Serving as entry points with distinct names, triggers are instrumental in activating multiple actions.

  • Rules: Rules establish an association between a trigger and an action. Consequently, when a trigger is fired, all associated actions are invoked accordingly.

The ops command

Let’s now provide an overview of OpenServerless’ command line interface, focusing on the ops command.

The command can be dowloaded in precompile binary format for many platform following the Download button on https://www.nuvolaris.io/

The ops command is composed of many commands, each one with many subcommands. The general format is:

ops <entity> <command> <parameters> <flags>

Note that <parameters> and <flags> are different for each <command>, and for each <entity> there are many subcommands.

The CLI shows documention in the form of help output if you do not provide enough parameters to it. Start with ops to get the list of the main commands. If you type the ops <entity> get the help for that entity, and so on.

For example, let’s see ops output (showing the command) and the more frequently used command, action, also showing the more common subcommands, shared with many others:

$ ops
Welcome to Ops, the all-mighty OpenServerless Build Tool

The top level commands all have subcommands.
Just type ops <command> to see its subcommands.

Commands:
    action      work with actions
    activation  work with activations
    invoke      shorthand for action invoke (-r is the default)
    logs        shorthand for activation logs
    package     work with packages
    result      shorthand for activation result
    rule        work with rules
    trigger     work with triggers
    url         get the url of a web action$ wsk action

There are many more sub commands used for aministrative purposes. In this documentation we only focus on the subcommands used to manage the main entities of OpenServerless.

Keep in mind that commands represent entities, and their subcommands follow the CRUD model (Create, Retrieve via get/list, Update, Delete). This serves as a helpful mnemonic to understand the ops command’s functionality. While there are exceptions, these will be addressed throughout the chapter’s discussion. Note however that some subcommand may have some specific flags.

Naming Entities

Let’s see how entities are named.

Each user also has a namespace, and everything a user creates, belongs to it.

The namespace is usually created by a system administrator.

Under a namespace you can create triggers, rules, actions and packages.

Those entities will have a name like this:

  • /mirella/demo-triggger

  • /mirella/demo-rule

  • /mirella/demo-package

  • /mirella/demo-action

When you create a package, you can put under it actions and feeds. Those entities are named

  • /mirella/demo-package/demo-action

  • /mirella/demo-package/demo-feed

💡 NOTE

In the commands you do not require to specify a namespace. If your user is mirella, your namespace is /mirella, and You type demo-package to mean /mirella/demo-package, and demo-package/demo-action to mean /mirella/demo-package/demo-action.

1 - Packages

How to group actions and their related files

Packages

OpenServerless groups actions and feeds in packages under a namespace. It is conceptually similar to a folder containing a group of related files.

A package allows you to:

  • Group related actions together.

  • Share parameters and annotations (each action sees the parameters assigned to the package).

  • Provide web actions with a common prefix in the URL to invoke them.

For example, we can create a package demo-package and assign a parameter:

$ ops package create demo-package -p email no-reply@nuvolaris.io
ok: created package demo-package

This command creates a new package with the specified name.

Package Creation, Update, and Deletion

Let’s proceed with the commands to list, get information, update, and finally delete a package:

First, let’s list our packages:

$ ops package list

packages
/openserverless/demo-package/ private

If you want to update a package by adding a parameter:

$ ops package update demo-package -p email info@nuvolaris.io

ok: updated package demo-package

Let’s retrieve some package information:

$ ops package get demo-package -s
package /openserverless/demo-package/sample:
    (parameters: *email)

Note the final -s, which means “summarize.”

Finally, let’s delete a package:

$ ops package delete demo-package

ok: deleted package demo-package

Adding Actions to the Package

Actions can be added to a package using this command:

ops action create <package-name>/<action-name>

This associates an existing action with the specified package.

Using Packages

Once a package is created, actions within it can be invoked using their full path, with this schema: <package-name>/<action-name>. This allows organizing actions hierarchically and avoiding naming conflicts.

Conclusion

Packages in OpenServerless provide a flexible and organized way to manage actions and their dependencies. Using the Ops CLI, you can efficiently create, add actions, and manage package dependencies, simplifying the development and management of serverless applications.

2 - Actions

Functions, the core of OpenServerless

Actions

An action can generally be considered as a function, a snippet of code, or generally a method.

The ops action command is designed for managing actions, featuring frequently utilized CRUD operations such as list, create, update, and delete. We will illustrate these operations through examples using a basic hello action. Let’s assume we have the following file in the current directory:

The hello.js script with the following content:

function main(args) {
    return { body: "Hello" }
}

Simple Action Deployment

If we want to deploy this simple action in the package demo, let’s execute:

$ ops package update demo
ok: updated package demo
$ ops action update demo/hello hello.js
ok: update action demo/hello

Note that we ensured the package exists before creating the action.

We can actually omit the package name. In this case, the package name is default, which always exists in a namespace. However, we advise always placing actions in some named package.

💡 NOTE

We used update, but we could have used create if the action does not exist because update also creates the action if it does not exist and updates it if it is already there. Update here is similar to the patch concept in REST API. However, create generates an error if an action does not exist, while update does not, so it is practical to always use update instead of create (unless we really want an error for an existing action for some reason).

How to Invoke Actions

Let’s try to run the action:

$ ops invoke demo/hello
{
    "body": "Hello"
}

Actually, the invoke command does not exist, or better, it’s just a handy shortcut for ops action invoke -r.

If you try to run ops action invoke demo/hello, you get:

$ ops action invoke demo/hello
ok: invoked /_/demo/hello with id fec047bc81ff40bc8047bc81ff10bc85

You may wonder where the result is. In reality, in OpenServerless, all actions are by default asynchronous, so what you usually get is the activation id to retrieve the result once the action is completed.

To block the execution until the action is completed and get the result, you can either use the flag -r or --result, or use ops invoke.

Note, however, that we are using ops to invoke an action, which means all the requests are authenticated. You cannot invoke actions directly without logging into the system first.

However, you can mark an action to be public by creating it with --web true (see below).

Public Actions

If you want an action to be public, you can do:

$ ops action update demo/hello hello.js --web true
ok: updated action demo/hello
$ ops url demo/hello
https://nuvolaris.dev/api/v1/web/mirella/demo/hello

and you can invoke it with:

$ curl -sL https://nuvolaris.dev/api/v1/web/dashboard/demo/hello
Hello

Note that the output is only showing the value of the body field. This is because the web actions must follow a pattern to produce an output suitable for web output, so the output should be under the key body, and so on. Check the section on Web Actions for more information.

💡 NOTE

Actually, ops url is a shortcut for ops action get --url. You can use ops action get to retrieve a more detailed description of an action in JSON format.

After action create, action update, and action get (and the shortcuts invoke and url), we should mention action list and action delete.

The action list command obviously lists actions and allows us to delete them:

$ ops action list
/mirella/demo/hello                                                  private nodejs:18
$ ops action delete demo/hello
ok: deleted action demo/hello

Conclusion

Actions are a core part of our entities. A ops action is a self-contained and executable unit of code deployed on the ops serverless computing platform.

3 - Activations

Detailed records of action executions

Activations

When an event occurs that triggers a function, ops creates an activation record, which contains information about the function execution, such as input parameters, output results, and any metadata associated with the activation. It’s something similar to the classic concept of log.

How activations work

When invoking an action with ops action invoke, you’ll receive only an invocation id as an answer.

This invocation id allows you to read results and outputs produced by the execution of an action.

Let’s demonstrate how it works by modifying the hello.js file to add a command to log some output.

function main(args) {
    console.log("Hello")
    return { "body": "Hello" }
}

Now, let’s deploy and invoke it (with a parameter hello=world) to get the activation id:

$ ops action update demo/hello hello.js
ok: updated action demo/hello
$ ops action invoke demo/hello
ok: invoked /_/demo/hello with id 0367e39ba7c74268a7e39ba7c7126846

Associated with every invocation, there is an activation id (in the example, it is 0367e39ba7c74268a7e39ba7c7126846).

We use this id to retrieve the results of the invocation with ops activation result or its shortcut, just ops result, and we can retrieve the logs using ops activation logs or just ops logs.

$ ops result 0367e39ba7c74268a7e39ba7c7126846
{
    "body": "Hello"
}
$ ops logs 0367e39ba7c74268a7e39ba7c7126846
2024-02-17T20:01:31.901124753Z stdout: Hello

List of activations

You can list the activations with ops activation list and limit the number with --limit if you are interested in a subset.

$ ops activation list --limit 5
Datetime            Activation ID                    Kind      Start Duration   Status  Entity
2024-02-17 20:01:31 0367e39ba7c74268a7e39ba7c7126846 nodejs:18 warm  8ms        success dashboard/hello:0.0.1
2024-02-17 20:00:00 f4f82ee713444028b82ee71344b0287d nodejs:18 warm  5ms        success dashboard/hello:0.0.1
2024-02-17 19:59:54 98d19fe130da4e93919fe130da7e93cb nodejs:18 cold  33ms       success dashboard/hello:0.0.1
2024-02-17 17:40:53 f25e1f8bc24f4f269e1f8bc24f1f2681 python:3  warm  3ms        success dashboard/index:0.0.2
2024-02-17 17:35:12 bed3213547cc4aed93213547cc8aed8e python:3  warm  2ms        success dashboard/index:0.0.2

Note also the --since option, which is useful to show activations from a given timestamp (you can obtain a timestamp with date +%s).

Since it can be quite annoying to keep track of the activation id, there are two useful alternatives.

With ops result --last and ops logs --last, you can retrieve just the last result or log.

Polling activations

With ops activation poll, the CLI starts a loop and displays all the activations as they happen.

$ ops activation poll

Enter Ctrl-c to exit.
Polling for activation logs

Conclusion

Activations provide a way to monitor and track the execution of functions, enabling understanding of how code behaves in response to different events and allowing for debugging and optimizing serverless applications.

4 - Sequences

Combine actions in sequences

Sequences

You can combine actions into sequences and invoke them as a single action. Therefore, a sequence represents a logical junction between two or more actions, where each action is invoked in a specific order.

Combine actions sequentially

Suppose we want to describe an algorithm for preparing a pizza. We could prepare everything in a single action, creating it all in one go, from preparing the dough to adding all the ingredients and cooking it.

What if you would like to edit only a specific part of your algorithm, like adding fresh tomato instead of classic, or reducing the amount of water in your pizza dough? Every time, you have to edit your main action to modify only a part.

Again, what if before returning a pizza you’d like to invoke a new action like “add basil,” or if you decide to refrigerate the pizza dough after preparing it but before cooking it?

This is where sequences come into play.

Create a file called preparePizzaDough.js

function main(args) {

  let persons = args.howManyPerson;

  let flour = persons * 180; // grams
  let water = persons * 120; // ml

  let yeast = (flour + water) * 0.02;

  let pizzaDough =
    "Mix " +
    flour +
    " grams of flour with " +
    water +
    " ml of water and add " +
    yeast +
    " grams of brewer's yeast";

  return {
    pizzaDough: pizzaDough,
    whichPizza: args.whichPizza,
  };
}

Now, in a file cookPizza.js

function main(args) {

  let pizzaDough = args.pizzaDough;
  let whichPizza = args.whichPizza;

  let baseIngredients = "tomato and mozzarella";
  if (whichPizza === "Margherita") {
    return {
      result:
        "Cook " +
        pizzaDough +
        " topped with " +
        baseIngredients +
        " for 3 minutes at 380°C",
    };
  } else if (whichPizza === "Sausage") {
    baseIngredients += "plus sausage";
    return {
      result:
        "Cook " +
        pizzaDough +
        " topped with " +
        baseIngredients +
        ". Cook for 3 minutes at 380°C",
    };
  }
}

We have now split our code to prepare pizza into two different actions. When we need to edit only one action without editing everything, we can do it! Otherwise, we can now add new actions that can be invoked or not before cooking pizza (or after).

Let’s try it.

Testing the sequence

First, create our two actions

ops action create preparePizzaDough preparePizzaDough.js

ops action create cookPizza cookPizza.js

Now, we can create the sequence:

ops action create pizzaSequence --sequence preparePizzaDough,cookPizza

Finally, let’s invoke it

ops action invoke --result pizzaSequence -p howManyPerson 4 -p whichPizza "Margherita"

{
    "result": "Cook Mix 720 grams of flour with 480 ml of water and add 24 grams of brewer's yeast topped with tomato and mozzarella for 3 minutes at 380°C"
}

Conclusion

Now, thanks to sequences, our code is split correctly, and we are able to scale it more easily!

5 - Triggers

Event source that triggers an action execution

Triggers

Now let’s see what a trigger is and how to use it.

We can define a trigger as an object representing an event source that triggers the execution of actions. When activated by an event, associated actions are executed.

In other words, a trigger is a mechanism that listens for specific events or conditions and initiates actions in response to those events. It acts as the starting point for a workflow.

Example: Sending Slack Notifications

Let’s consider a scenario where we want to send Slack notifications when users visit specific pages and submit a contact form.

Step 1: Define the Trigger

We create a trigger named “PageVisitTrigger” that listens for events related to user visits on our website. To create it, you can use the following command:

ops trigger create PageVisitTrigger

Once the trigger is created, you can update it to add parameters, such as the page parameter:

ops trigger update PageVisitTrigger --param page homepage

💡 NOTE

Of course, there are not only create and update, but also delete, and they work as expected, updating and deleting triggers. In the next paragraph, we will also see the fire command, which requires you to first create rules to do something useful.

Step 2: Associate the Trigger with an Action

Next, we create an action named “SendSlackNotification” that sends a notification to Slack when invoked. Then, we associate this action with our “PageVisitTrigger” trigger, specifying that it should be triggered when users visit certain pages.

To associate the trigger with an action, you can use the following command:

ops rule create TriggerRule PageVisitTrigger SendSlackNotification

We’ll have a better understanding of this aspect in Rules

In this example, whenever a user visits either the homepage or the contact page, the “SendSlackNotification” action will be triggered, resulting in a Slack notification being sent.

Conclusion

Triggers provide a flexible and scalable way to automate workflows based on various events. By defining triggers and associating them with actions, you can create powerful applications that respond dynamically to user interactions, system events, or any other specified conditions.

6 - Rules

Connection rules between triggers and actions

Rules

Once we have a trigger and some actions, we can create rules for the trigger. A rule connects the trigger with an action, so if you fire the trigger, it will invoke the action. Let’s see this in practice in the next listing.

Create data

First of all, create a file called alert.js.

function main() {
    console.log("Suspicious activity!");
    return {
        result: "Suspicious activity!"
    };
}

Then, create a OpenServerless action for this file:

ops action create alert alert.js

Now, create a trigger that we’ll call notifyAlert:

ops trigger create notifyAlert

Now, all is ready, and now we can create our rule! The syntax follows this pattern: “ops rule create {ruleName} {triggerName} {actionName}”.

ops rule create alertRule notifyAlert alert

Test your rule

Our environment can now be alerted if something suspicious occurs! Before starting, let’s open another terminal window and enable polling (with the command ops activation poll) to see what happens.

$ ops activation poll
Enter Ctrl-c to exit.
Polling for activation logs

It’s time to fire the trigger!

$ ops trigger fire notifyAlert
ok: triggered /notifyAlert with id 86b8d33f64b845f8b8d33f64b8f5f887

Now, go to see the result! Check the terminal where you are polling activations now!

Enter Ctrl-c to exit.
Polling for activation logs

Activation: 'alert' (dfb43932d304483db43932d304383dcf)
[
    "2024-02-20T03:15.15472494535Z stdout: Suspicious activity!"
]

Conclusion

💡 NOTE

As with all the other commands, you can execute list, update, and delete by name.

A trigger can enable multiple rules, so firing one trigger actually activates multiple actions. Rules can also be enabled and disabled without removing them. As in the last example, let’s try to disable the first rule and fire the trigger again to see what happens.

$ ops rule disable alertRule    
ok: disabled rule alertRule
$ ops trigger fire notifyAlert  
ok: triggered /_/notifyAlert with id 0f4fa69d910f4c738fa69d910f9c73af
  • Disabling the rule.

  • Firing the trigger again.

In the activation polling window, we can see that no action is executed now. Of course, we can enable the rule again with:

ops rule enable alertRule