# Serverless Workflow to Knative This document describes the rules for translating most of the [Serverless Workflow](https://github.com/serverlessworkflow/specification/blob/main/specification.md) features to [Knative](https://knative.dev). Knative Eventing defines three sets of APIs, Channel/Subscription Broker/Trigger and flows. The rules below target Broker/Trigger for simplicity reason. A Knative Trigger (as of today) can be represented as a 3-tuple with (Event, Filter, Subscriber) that moves from one state to another. Serverless Workflow is a declarative language with lots of *sugar* on top of the core features, for instance the reusable function and event definition sections and metadata. Below we are looking only at the core Serverless Workflow features. Translating non-core features to core features might at some point be documented. ## [Workflow State](https://github.com/serverlessworkflow/specification/blob/main/specification.md#workflow-states) ### General Approach A State is defines as a *building block of workflow control flow logic*. All states share these common set of attributes: * name: unique State name * type: state type * transition: transition to another state. Complex states (e.g. Switch State) can have more than one transitions * end: final state The State name can be represented by a CloudEvent attribute extension, let's call it `knstatename`. A state becomes active when the `knstatename` attribute value matches the state name. A State can be represented by a Knative Trigger: ```yaml= apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: <workflowname>-<statesname> spec: filter: attributes: knstatename: <statename> ... ``` The transition to another state is achieved by mutating the `knstatename` attribute when a state is completed. Here an example where the State action (dummy State) is to transition to the next state: ```yaml= apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: <workflowname>-<statesname> spec: subscriber: ref: apiVersion: serving.knative.dev/v1 kind: Service name: go-to-next-state uri: ?nextstatename=<transitionname> ... ``` > WARNING: this is outdated. Instead a single service containing the workflow states handles state transitions. Most of the time the Trigger's subscriber performs more complex operations, realized by a composition (i.e. mini-workflow) of builtin functions. These functions must be able to support transitioning to the next sub-state and a such they must accept `statename` as input and must return a mutated CloudEvent with `knstatename` set to the `statename` input. > **Alternative** Knative Trigger supports overriding ce attributes before forwarding the reply event to the broker > **Note** A Serverless Workflow State can mapped onto multiple Knative Triggers. See below for examples. The final state can be represented by a Trigger with a builtin subscriber (TODO) #### [Workflow data](https://github.com/serverlessworkflow/specification/blob/main/specification.md#workflow-data) There are various solutions to represent the workflow data, for instance it can be encoded as part of the CloudEvent metadata or persisted in a global external store. Serverless Worflow does not have the notion of global state data which makes the first solution possible, at least when the state data is *small enough*. This document is fairly agnostic about where the state data lives. #### Merging Data > Consumed event data (payload) and action execution results should be merged into the state data. ### Actions #### FunctionRef Serverless Workflow supports transforming the state data to a list of argument. ### [Event State](https://github.com/serverlessworkflow/specification/blob/main/specification.md#Event-State) > WARNING: this feature might change https://github.com/serverlessworkflow/specification/discussions/581 due to the lack of clarity ``` onEvents: <eventRefs array> eventRefs: <events array, <actions array> ``` Example: ```yaml= onEvents: - eventRefs: - HighBodyTemperature - HighBloodPressure actions: - functionRef: refName: sendTylenolOrder arguments: patientid: "${ .patientId }" - eventRefs: - HighBloodPressure actions: - functionRef: refName: callNurse arguments: patientid: "${ .patientId }" - eventRefs: - HighRespirationRate actions: - functionRef: refName: callPulmonologist arguments: patientid: "${ .patientId }" end: terminate: true ``` #### exclusive: false > Receiving one of the defined events causes its associated actions to be performed. Each `evenRefs` is translating to a Trigger. The first `eventRefs` in the example above corresponds to: ```yaml= kind: Trigger spec: filters: - any: - exact: <HighBodyTemperature> - exact: <HighBloodPressure> ... ``` #### exclusive: true > all of the defined events must be consumed in order for actions to be performed Question: does `exclusive` apply to all events in all `eventRefs` or all events in each `eventRefs`. For now we assume it is the former. One Trigger is defined per Event, after duplicate removal. The Trigger subscriber is the *Buffer* stateful operator (to define). #### Transition > The state goes to the next transition of the workflow after all the actions have been performed. ### [Operation State](https://github.com/serverlessworkflow/specification/blob/main/specification.md#Operation-State) > Operation state defines a set of actions to be performed in sequence or in parallel. Once all actions have been performed, a transition to another state can occur. ### [Inject State](https://github.com/serverlessworkflow/specification/blob/main/specification.md#inject-state) > Inject state can be used to inject static data into state data input. Inject state does not perform any actions. It is very useful for debugging, for example, as you can test/simulate workflow execution with pre-set data that would typically be dynamic in nature (e.g., function calls, events). ## Builtin functions ## References [The Event-Carried State Transfer pattern](https://itnext.io/the-event-carried-state-transfer-pattern-aae49715bb7f)