Skip to content

Reactors

Reactors are stateless collections of methods that can perform actions inside Hantera based on input. Reactors can be used to create custom APIs and automate background work.

A Reactors starts with a Component that is then configured as a Reactor instance. This makes Reactor components re-usable for many different use-cases.

Reactors are usually called from Rules or through it’s public API. Reactor calls can also be scheduled to run at some interval or in the future.

Reactors methods can take arguments, and can return values. Given the nature of Reactors, besides being a bridge between Hantera’s Actors and the outside world, they can often replace serverless functions provided by my of today’s cloud providers.

A Simple Example

To get a better idea of what a Reactor looks like, take a look at the below Reactor Component:

param uri: text
from {
post = args => httpPost(uri) with {
body = args.body
}
}

This very simple Reactor allows Rules to make external HTTP requests towards a predetermined URL. Let’s break it down.

param uri: text

This line defines a Reactor Parameter called uri or type text. This is used later to determine the endpoint of the requests. Reactor paramters are set when the Reactor is created. The same component can be re-used in multiple reactors if we need to make requests to many different endpoints.

from

The from keyword makes up the main output of the Reactor. Reactors must only have a single return value, and it must be a record of method handlers.

post = args => httpPost(uri) with {
body = args.body
}

This part defines a record field called post. This will be the name of the method when we call it later. The part that comes next is a function definition which is the handler that will be used to generate the method effects. Method effects are declaractions of effects that the Reactor system should execute. In this case, we’re telling the system that we want to perform an HTTP POST request.

To simplify declaring effects, the Reactor runtime provides builder functions, such as httpPost in this case. There are more effects available in the Reactor Runtime Reference.

httpPost returns a record that defines the effect. In this case, we’re adding an additional proeprty body which will be included in the final request. The args.body means that we’re expecting to get the body as a method argument. This means that any Rule that calls this method, can provide the body to be used for the request.

We don’t have to specify any type of the body, it will be inferred as the Reactor runtime knows that the body property of requests must be text.

Testing the Reactor

In order to test this reactor, we must first install the component in Hantera. For the sake of this guide, we will use a manifest file and push the component to Hantera using hantera-cli. We will then create the Reactor instance itself using another manifest file referencing the newly created component.

  1. Start by creating the manifest file for Reactor component:

    component.myReactors.httpPoster.h_manifest.yaml
    uri: /resources/components/myReactors/httpPoster
    spec:
    displayName: 'Http Poster'
    description: 'Makes a POST request to a configured endpoint, allowing the caller to set request body'
    code: |-
    param uri: text
    from {
    post = args => httpPost(uri) with {
    body = args.body
    }
    }
  2. Next, let’s define the manifest that will instantiate the Reactor resource:

    reactor.my-api.h_manifest.yaml
    uri: /resources/reactors/my-api
    spec:
    componentId: myReactors/httpPoster
    parameters:
    uri: https://my-api.example.com/v1/endpoint
    1. Now let’s apply these manifests to our Hantera instance:
    expressiveCode.terminalWindowFallbackTitle
    h_ manage apply component.myReactor.httpPoster.h_manifest.yaml
    h_ manage apply reactor.my-api.h_manifest.yaml
  3. You should now have a new Reactor resource live in your Hantera. You can verify it by querying the Reactors API. This should return metadata about the Reactor and it’s available methods:

    GET https://<hantera-hostname>/resources/reactors/my-api
    Authorization: Bearer <YOUR TOKEN>
  4. Once you have verified that the Reactor is there, you can now call the method using the API:

    POST https://<hantera-hostname>/resources/reactors/my-api/post
    Authorization: Bearer <YOUR TOKEN>
    Content-Type: application/json
    {
    "body": "This will be the body of the request"
    }

That’s it. Notice how the method name defined in the Reactor component code is manifested in it’s public API, while we can name the Reactor anything we want in the actual Reactor resource.

A Note About Security

You may wonder, how is it safe to make arbitrary external requests available through the public API. This sounds like a recipe for all kinds of denial-of-service problems.

While it’s true that Reactors are powerful and used wrong it can be misused. Reactors are tightly integrated with Hantera’s access control layer, which means that calling Reactor Methods require explicit permission for the session. Additinally, as opposed to many other similar platforms, Hantera makes all effects of Reactors very easy to monitor. They are just data, after all. With clear visibility of effects combined with thoughtful design of Reactor Components, Hantera’s Reactors are safer than many other similar platforms on the market today.

Calling Reactor Methods From Rules

Rules can be combined with Reactors to create advanced automated reactions within Hantera based on various events. For example, you might want to send an email to the Customer when the Order is confirmed. With a Rule that triggers on an Order state change, this becomes trivial.

In the following example, we assume that we already have a Reactor that uses Mailtrap to send e-mails called mailtrap with method sendEmailFromTemplate. You can find this exact example in the Cookbook

The Rule effect used to call methods is callReactor, and can be created easily using the effects.common.callReactor function. Not all Rule Hooks support the callReactor effect. Refer to the Rule Runtime Hooks Reference for more details.

order-confirmation.hrule
param input: OnOrderCommands
param templateUuid: text
param reactorId = 'mailtrap'
let sendOrderConfirmation = effects.common.callReactor(reactorId, 'sendEmailTemplate', {
to = {
email = input.order.invoiceAddress.email
name = input.order.invoiceAddress.name
}
templateUuid = templateUuid
templateVariables = {
customer = {
name = input.order.invoiceAddress.name
address = {
firstName = input.order.invoiceAddress.name
street = input.order.invoiceAddress.addressLine1
city = input.order.invoiceAddress.city
state = input.order.invoiceAddress.state
zip = input.order.invoiceAddress.postalCode
country = input.order.invoiceAddress.countryCode
}
}
order = {
number = input.order.orderNumber
items =
input.order.deliveries
select d => d.orderLines
flatten
select l => {
name = l.description
quantity = l.quantity
price = l.unitPrice
}
isRush = false
total = input.order.orderTotal
}
}
})
from input match
{
before: {
orderState: 'pending'
}
order: {
orderState: 'confirmed'
}
} |> sendOrderConfirmation

Access Control

The ACE pattern for reactors looks like this:

reactor[/<id>][/<method>]:read|write|call