Fulfillments
A fulfillment is used to retrieve, send or process data in APICHAP. Everytime an operation is executed it will run the list of fulfillments specified in your operation. All the data retrieved within a fulfillment will be temporarily stored within the current operation context to use in the upcoming fulfillments and the response.
Typical examples for a fulfillment you might create:
- A READ fulfillment to SELECT data from a database.
- A UPDATE fulfillment to create and write into a file on an FTP-SERVER.
- A READ fulfillment to download a CSV file from an URL.
- A LOOP fulfillment to loop over an incoming JSON array requestbody.
Depending on the type a fulfillment has a different purpose.
The following types are available:
- READ: Retrieve data from a datasource.
- UPDATE: Update data in your datasource.
- LOOP: Run a list of fulfillments multiple times in a loop.
- IF: Create an if - else branch and define a list of fulfilments to be run in either the if or else branch.
- GENERIC: A generic fulfillment can be used to do many things, not related to READ or UPDATE data in a datasource. Usually used to prepare attributes or handle data checks.
- REFERENCE: A way to execute another operation from the current one.
The types READ and WRITE are mostly very similar and will only make a difference when the instruction has an explicit differently process to read or write data. For example the case when calling an SQL query.
Fulfillments contain the following attributes:
Field | Used in Type | Description | Mandatory |
---|---|---|---|
name | All | Set a fulfillment name. You may use the name later to use your fulfillment results in conditions or response building. | x |
type | All | Define the type of your fulfillment. READ, UPDATE or LOOP | x |
if | All | Specifies an if condition. The fulfillment should only be processed under certain conditions. | |
attributes | All | Define a list of attributes usable in the whole operation context. | |
exceptions | All | Use exceptions to immediately return a HTTP response to the user. | |
sleep | All | Time in milliseconds the thread will sleep before executing the fulfillment. | |
datasource | READ, UPDATE | The datasource this fulfillment should use to retrieve data from. Use the datasource name from the datasources section. | x |
instructions | READ, UPDATE | Define your instructions. An instruction is the actual command on how to retrieve data from your datasource. Like a SQL query, Http-Call, ... | x |
loop | LOOP | Define a JSONArray to loop through and process each entry with a list of sub fulfillments. | |
fulfillments | LOOP | In combination with loop you are able to define a list of fulfillments to run in a loop | |
loopbreak | LOOP | A list of conditions. If they match, the current loop is aborted and apichap will continue with the next fulfillment. | |
index | LOOP | If you run a fulfillment multiple times in a loop, you are able to index each iteration by a value. | |
then | If | Only in combindation with fulfillment type if . Define a list fulfillments executed when the if condition matches to true. |
|
else | If | Only in combindation with fulfillment type if . Define a list fulfillments executed when the if condition matches to false. |
|
operationName | REFERENCE | Only in combindation with fulfillment type reference . Define the operation you would like to trigger. |
|
parameters | REFERENCE | Only in combindation with fulfillment type reference . Define parameters for the operation trigger. |
Fulfillment Name
Rules for naming your fulfillments:
- Fulfillment names are no longer mandatory, however recommended for easier logging.
- Fulfillment names must not contain dots
- Spaces are not recommended
IF
Whenever you want to process a fulfillment only on a certain condition. For example, if
the input Header-Parameter
'Content-Language' is available. This can be achieved with the if
condition.
# Only run fulfillment if Content-Language header is not empty or null
if: PARAM(Content-Language) != NULL()
# Only run a fulfillment if another fulfillment called 'ff1' (already executed) has at least one entry
if: SIZE(VALUE(ff1, $)) > INTEGER(0)
Exceptions
In order to handle error cases you are able to specify exception for each fulfillment. Whenever an exception is triggered the processing of the fulfillments is aborted and the exception´s response is returned directly. It´s possible to define multiple exceptions.
Field | Description | Mandatory |
---|---|---|
if | Define an if condition under which you want to trigger this exception | |
statuscode | The http status code you want to return for the error case. e.g.: 404 | x |
message | The response message you want to return | x |
A classical case is to return HTTP-code 404 when an item is not available in the database. Your exception for this case could look like this:
# we have a fulfillment getting a news entry from a database and want to return 404 if the news id does not exist.
name: ffnewsdetail
type: read
datasource: db
instructions:
- sql:
query: Select * from news WHERE id = ?
parameters:
- param: PARAM(id)
exceptions:
- if: SIZE(VALUE(ffnewsdetail, $)) <= INTEGER(0)
statuscode: 404
message: "Item is not available"
Instructions
Instructions are the process to communicate with your datasource and call for informations. instructions
are the actual place to specify
SQL queries, HTTP-requests, ...
A instruction is only allowed for READ & WRITE fulfillments
Therefore depending on the type of datasource multiple types of configuration options are available:
Instructions IF
No matter what kind of datasource you are using you are always able to set a list of instructions with an if condition. This way it´s possible to send different queries depending on certain conditions.
Field | Description | Mandatory |
---|---|---|
if | Define an if condition under which you want to use this query | |
query / request / file | Depending on your datasource you either have a query, request, ... | x |
Database / SQL
A database instruction can be used to query direct SQL to your database.
Field | Description |
---|---|
transaction | The SQL query |
transaction | Optional parameters to build your prepared statement |
transaction | Define an transaction name for SQL transactional commits. |
commit | Commit your transaction. (Only necessary if you use SQL Transaction) |
Build your Query
A basic SQL instruction consists of your SQL query and optional parameters. Use ? to build your prepared statement and specify the parameters below.
instructions:
- sql:
query: SELECT id FROM news WHERE lang = ?
parameters:
- param: PARAM(Content-Language)
Even though it´s possible to use placeholder values ${...} directly in your query, its highly important to not use that for your user input parameters. To avoid the risk of SQL injections you must use parameters and ? for that.
Optional Filters
Often you want to build a SQL query depending on optional filter parameters retrieved. That is possible by using the if
in the parameters and preparing attributes. This could be an example on how to build such a SQL with optional filters:
- name: prepare_filter
summary: Prepare the sql statement for optional filters.
type: generic
attributes:
- if: PARAM(startdate) != NULL()
startdateSQL: TEXT(AND e.startdate >= ?)
- if: PARAM(enddate) != NULL()
enddateSQL: TEXT(AND e.enddate <= ?)
- name: get_filtered_data_events
summary: Get a list of future events including an attendees count based on filters.
type: read
datasource: sample-events
instructions:
- sql:
query: SELECT * FROM events WHERE 1 = 1 ${ATTRIBUTE(startdateSQL)} ${ATTRIBUTE(enddateSQL)}
parameters:
- if: PARAM(startdate) != NULL()
param: PARAM(startdate)
- if: PARAM(enddate) != NULL()
param: PARAM(enddate)
Database Response
Every result from a fulfillment (also database results) are transformed into a JSON and accessible with the VALUE method.
The result from a database is transformed in the following JSON set to work with:
id | title | description |
---|---|---|
1 | News 1 | Newsttext 1 |
2 | News 2 | Newstext 2 |
[
{
"id": 1,
"title": "News 1",
"description": "Newsttext 1"
},
{
"id": 2,
"title": "News 2",
"description": "Newsttext 2"
}
]
A database query always produces a JSON Array, because we don´t know how many results are returned in a query. Meaning in your JSONpath you have to use $[0].val1 if you are sure only one item is returned.
Generated Keys
When running an INSERT or UPDATE statement APICHAP will always get the Generated Key
from the database.
Depending on your database the Generated Key
looks differently:
Database | Get Generated Keys |
---|---|
MariaDb | If you run an INSERT command with an auto-incremental ID, you can use VALUE(fulfillment_name, $.insert_id) afterward which provides the generated id. |
PostgreSQL | PostgreSQL will deliver all columns that have been created in the INSERT/UPDATE query. Just like if you would query a select. VALUE(fulfillment_name, $.id) |
MSSQL | You will get the auto-incremental ID as GENERATED_KEYS. e.g.: VALUE(fulfillment_name, $[0].GENERATED_KEYS)) |
SQL Transactions
In basic mode SQL queries are using auto-commit. Meaning they are automatically executed. When processing bigger amount of data or you have big updates and want to use the SQL rollback functionality you have to use SQL Transactions.
SQL Transactions are limit to one operation and one call per operation. It´s not possible to build SQL transactions over multiple operations.
To define your SQL Transaction command simply add the key transaction
to your instruction and use a name for this one. It´s possible to use multiple transactions at the same time, differentiated by their name.
instructions:
- sql:
transaction: transactional_insert
query: INSERT INTO news (title, content, lang) VALUES (?,?,?)
parameters:
- param: BODY($.title)
- param: BODY($.description)
- param: PARAM(Content-Language)
An instruction having the transaction key will not be initially executed but is stored locally until it´s committed.
Afterwards commit all SQL queries for a certain transaction by using commit: transactionName
. This will execute all collected SQL queries and rollback to the initial state if there is a failure.
- name: commit
type: UPDATE
datasource: my_db
instructions:
- sql:
commit: transactional_insert
allowFailure: true
Request
A request is specified by setting a ´request´ attribute in your yaml.
Within this ´request´ attribute you are able to set the following attributes:
Field | Description | Mandatory |
---|---|---|
url | The URL for the request to send. | x |
httpType | Your httpType from our supported list: GET, POST, PUT, DELETE. | x |
contentType | The contentType specifies | |
header | Set http header parameter in the header section. | |
schema | Specify an schema to send with the requestbody. |
|
multipart | Send a multipart request | |
formBody | Send a formdata request | |
csvSeparator | Set the csvSeperator if you are reading or writing csv. | |
allowFailure | Allow failure of an request. (Default: false) | |
ignoreCert | Disable SSL Check (Default: False) | |
timeout | Change the default timeout in seconds (Default: 10 seconds) | |
gzip | Force to decrypt the response with gzip. (Default: False) |
ContentType
Specify which contentTyp of you´re request and response:
application/json
text/xml
application/xml
text/csv
text/plain
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.ms-excel
Your request will be automatically transformed in your requested dataformat. Take a look at the Dataformat section to understand the process behind it.
Header
Specify headers by using a ´Key: Value´ approach.
instructions:
- request:
url: "https://www.example.com/news"
httpType: GET
contentType: application/json
header:
- "Content-Language: ${TEXT(en)}"
- "X-UserId: ${VALUE(ff1, $.userid)}"
When specified a Content-Type in the header it will overwrite the APICHAP defined
contentType
field.
Sending a Requestbody
Find more information on how to build your schema
here.
instructions:
- request:
url: "https://www.example.com/user"
httpType: POST
header:
- "Authorization: ${PARAM(Authorization)}"
contentType: application/json
schema:
email: PARAM(email)
name: PARAM(name)
userType: TEXT(user)
phone:
list: LIST(1)
item:
id: PARAM(phone)
Sending a Multipart Requests
If you need to send a multipart request you can do so by specifying the multipart
attribute.
This example shows you how you are using an incoming multipart file within the key image and send it to another request.
instructions:
- request:
url: https://www.example.com
httpType: POST
header:
- "Content-Type: image/jpeg"
multipart:
filepart:
contentType: TEXT(plain/text)
file:
name: TEXT(file.txt)
content: TEXT("Hello World")
jsonpart:
contentType: application/json
schema:
hello: TEXT(world)
Sending a Form Data Requests (formBody)
If you need to send a form data request you can do so by specifying the formBody
attribute. This section allows you to easily add key-value pairs to your form body request.
instructions:
- request:
url: https://www.example.com
httpType: POST
formBody:
key1: VALUE(val)
key2: TEXT(val2)
AllowFailure
When a request or SQL Transaction commit does not deliver a successful result the current process is aborted and an 500 HTTP error is returned.
By using the flag allowFailure
you are able to control this behaviour. With allowFailure: true
the workflow will continue and call the next fulfillments.
Since your fulfillment was aborted and does not have any data result, the result can be checked using the EXCEPTION(ffName)
method and the detailed statuscode with STATUSCODE(ffName)
.
# Call a request and continue when request failes
- name: ff1
type: READ
datasource: my_api
- request:
url: "https://www.example.com/user"
httpType: GET
header:
- "Authorization: ${PARAM(Authorization)}"
allowFailure: true
- name: call_successful
type: if
if: STATUSCODE(ff1) == INTEGER(404)
then:
## Handle a 404
- name: commit
type: UPDATE
datasource: my_database
instructions:
- sql:
commit: transactional_insert
allowFailure: true
- name: commit_succesful
type: if
if: EXCEPTION(commit) != NULL()
then:
## Do SOMETHING IF call was successful
else:
## Handle failure
File
When using an FTP datasource you should build a file fulfillment. The FTP datasource supports the following variables when working with files:
folderPath
: The folder path on the FTP server.filePath
: The file path (including the file name) on the FTP server.contentType
: The MIME type of the file (e.g.,application/xml
).csvSeparator
: If working with CSV files, specify the separator.move
: The path to move the file after processing (optional).schema
: A map containing the schema for reading or writing files.
Creating a file
- type: update
datasource: myftp
instructions:
- file:
filePath: /xxx/File_${DATEFORMAT(NOW(), "yyyy-MM-dd'T'HHmmss")}-${ATTRIBUTE(shipmentID)}.xml
contentType: application/xml
schema:
xxxx:
Listing files in a folder
Reading a file in a loop
- name: files_loop
type: loop
loop: VALUE(files, $)
fulfillments:
- name: myproducts
type: read
datasource: myftp
instructions:
- file:
filePath: /${VALUE(files_loop, $.filename)}
contentType: application/xml
exceptions:
- if: SIZE(VALUE(myproducts, $.response.xml.artikel)) <= INTEGER(0)
statuscode: 404
message: File not available or empty
Moving a file
- type: update
datasource: myftp
instructions:
- file:
filePath: /${VALUE(files_loop, $.filename)}
move: /processed/${DATEFORMAT(NOW(), "yyyy-MM-dd'T'HHmmss")}_${VALUE(files_loop, $.filename)}
You can specify the CSV separator using the
csvSeparator
field, and the content type for CSV files should betext/csv
.
Attributes
With this feature, you can define and store attributes (like variables in programming) within each fulfillment. These attributes can hold values such as strings, numbers, or other data types, and can be reused later in the operation.
How to Define Attributes
Attributes can be defined within any fulfillment and will be available throughout the operation after they are created. For example:
- name: update_project_status
type: UPDATE
datasource: middleware_db
instructions:
- sql:
query: UPDATE project SET status = 'failure' WHERE uuid = ?
parameters:
- param: PARAM(projectid)
attributes:
- status: VALUE(update_project_status, $[0].status)
In this example, the attribute status
is created and assigned the result of the VALUE
function, which can be reused later in the operation.
Conditional Attributes
You can also define attributes conditionally by using an if
clause inside the attributes section:
- type: GENERIC
attributes:
- if: PARAM(lang) == TEXT(DE)
language: TEXT(GERMAN)
org_lang: TEXT(DEUTSCH)
- another_attt: TEXT(test)
Here, the attributes language
and org_lang
are only set if the condition PARAM(lang) == TEXT(DE)
evaluates to true.
Using Attributes
Once an attribute is defined, it can be referenced later in the operation using the ATTRIBUTE
function:
For example, you can use ATTRIBUTE(org_lang)
to retrieve the value of the org_lang
attribute defined earlier.
Loop
Sometimes you need to run a list of fulfillments in a loop. For example if you have a requestbody and need to process each entry. This is possible by defining a loop with a list of sub fulfillments. A loop fulfillment holds the following attributes:
Field | Description | Mandatory |
---|---|---|
name | Set a fulfillment name. You may use the name later to use your fulfillment results in conditions or response building. | x |
type | LOOP | x |
loop | Define a JSONArray you want to loop through. | x |
fulfillments | In combination with loop you are able to define a list of fulfillments to run in a loop | x |
Within a loop you are able to get the current loop json by using the loop´s fulfillment name.
A loop that wants to insert every entry of the POST requestbody array would look like this:
'post /news/bulk':
fulfillments:
- name: loop_news_body
type: LOOP
loop: BODY($.news)
fulfillments:
# Insert the news entry
- name: news-insert
type: UPDATE
datasource: mydatabase
instructions:
- sql:
query: INSERT INTO news (id, title) VALUES (?,?)
parameters:
- VALUE(loop_news_body, $.id)
- VALUE(loop_news_body, $.properties.title)
Fulfillment Results within a loop are not available outside of the loop.
Loopbreak
Let´s assume you are calling a paged GET /products request in a loop until your paged request delivers no more results. A typical case for a loopbreak. A loopbreak can be set to each fulfillment (not the loop fulfillment directly) and holds a list of conditions. After the fulfillment is ran it checks if any of those conditions are true and if so aborts the loop and continues with the next fulfillments outside of it.
# Call a paged request by using loop(10) but abort the loop as soon as the request page is empty.
- name: loop_ff
type: loop
loop: LIST(20)
fulfillments:
# Retrieve inventory per page
- name: get_inventory
type: READ
datasource: api
instructions:
- request:
url: "https://www.example.com?page=${VALUE(inventory_page, $)}"
httpType: GET
loopbreak:
- SIZE(VALUE(get_inventory, $.inventory)) <= INTEGER(0)
Index
If a fulfillment retrieves data and is run multiple times because its part of a loop you need a way to reference which data you want to access afterwards. Therefore define the index
value to an unique identifier. Afterwards you are able to access specific data results of the fulfillment using the VALUE_BY_INDEX method.
- name: loop_ff
type: loop
loop: VALUE(products, $)
fulfillments:
# Retrieve inventory per page
- name: get_inventory
type: READ
datasource: api
instructions:
- request:
url: "https://www.example.com?page=${VALUE(inventory_page, $)}"
httpType: GET
index: VALUE(@, $.id)
Fulfillment Type: if
The fulfillment type if
allows conditional execution of fulfillments based on a logical expression. Depending on the result of the condition, different sets of fulfillments can be run for both the then
and else
cases.
Do not confuse this with the simple
if
condition available for every fulfillment. Aif
fulfillment type is usefull if you have more fulfillment depending on something.
Structure
type: if
: Indicates that this fulfillment checks a condition.if
: The condition to evaluate. If this condition istrue
, the fulfillments underthen
will execute. Otherwise, those underelse
will execute.then
: A list of fulfillments to run if the condition is true.else
: A list of fulfillments to run if the condition is false.
- type: if
if: SIZE(VALUE(gen_config, $.data.operations)) <= INTEGER(0)
then:
# Config could not be created
- name: update_project_status
type: UPDATE
datasource: middleware_db
instructions:
- query: UPDATE project SET status = 'failure' WHERE uuid = ${PARAM(projectid)}
attributes:
- status: VALUE(update_project_status, $[0].status)
else:
- type: UPDATE
datasource: middleware_db
instructions:
- query: INSERT INTO config_response (project_id, action_generation) VALUES
(
${PARAM(projectid)},
${GET_JSON(gen_config)}
)
Fulfillment Type: GENERIC
The GENERIC
fulfillment type is designed to handle non-data operations, such as checking conditions or setting attributes. It cannot be used to read from or write to a datasource, making it useful for logic-only operations within the flow of an operation.
Usage:
- No
instructions
related to reading or writing data are allowed. - Primarily used for setting attributes, performing validations, or conditional checks.