Create a new capability
Capability is an application functionality to serve business needs. It consists of one or more use-cases targeted at particular business scenarios that the capability is designed to solve.
Use-cases are defined through a Comlink profile. The Comlink profile is a file with .supr
extension. It specifies the abstract business behavior of the use-case: its input and result parameters, error behavior and data types.
This guide will walk you through the process of defining a new capability with a Comlink profile. You will use the Superface CLI to bootstrap a new profile and learn the syntax.
Setup
This guide assumes you have set up a Node.js project with Superface and OneSDK. If you need to set up a new project, please see the Setup Guide.
Create New Profile
Choose Profile Name
Profile's name must consist of lowercase letters, numbers, characters, dashes and underscores.
- Valid:
my_profile
,myprofile123
,my-profile
- Invalid:
my profile
,my+profile
,MyProfile
While single profile file can contain multiple use-cases, we generally recommend to keep single use-case per profile. So the profile can be named after the use-case, for example:
- Get weather:
get-weather
- Make payment:
make-payment
- Send email:
send-email
Scoped profiles
Profile name can contain scope for grouping profiles together.
To scope a profile, add scope-name/
before profile name, for example: communication/send-email
.
Bootstrap With CLI
You can use the Superface CLI to set up an empty Comlink profile:
superface create --profile --profileId <use_case_name>
Where <use_case_name>
is the name of use-case you wish to create.
CLI Help
Use the --help
flag for more options and examples:
superface create --help
The CLI creates use_case_name.supr
file in current directory and links to it from the superface/super.json
.
The new profile will look similarly to this:
name = "use_case_name"
version = "1.0.0"
"""
UseCaseName usecase
"""
usecase UseCaseName {}
Define the Use-Case
With the Comlink profile ready, you can now define your business use-case. The use-case is a task that needs to be done. You can think of it as a function with specified input and output parameters. The use-case can also specify its safety.
Overview
Let's take a look at an example use-case (based on the Shipment information capability):
"""
Retrieve Shipment Status
Get the current shipment status.
"""
usecase ShipmentInfo safe {
input {
"Shipment tracking number
Identifier of shipment"
trackingNumber! string!
"Carrier
Shipment carrier identification to narrow down the results"
carrier string!
}
result {
"Carrier
Name of the carrier responsible for delivery"
carrier string!
"Status
Description of the current shipment status"
status! string
"Origin
A postal address with the origin of the shipment"
origin string
"Destination
A postal shipping address"
destination string
events! [{
timestamp! string
statusText! string
}]
"Estimated date and time of delivery"
estimatedDeliveryDate
}
error {
title! string
detail string
}
}
At the outer level, the use case is documented with descriptions in triple quotes. The definition itself starts with usecase
keyword, the use-case is named ShipmentInfo
and is marked as safe
, so executing it shouldn't change anything (see below for safety).
The use-case consists of three blocks:
input
with fields required for use-case's execution,result
with expected fields from successful execution,error
describing the fields returned in case of execution error (e.g. due to failure on provider's end).
All these blocks consist of fields. The fields are documented with a single double quote (which is equivalent to triple quote, see descriptions). Most fields have their type defined (i.e. string
), but the typing is optional - estimatedDeliveryDate
field is untyped. The events
field is an array of objects with timestamp
and statusText
fields. See Field Types for more information about possible types. Some fields are marked with exclamation mark as required (e.g. trackingNumber! string
), and some are marked as non-null fields (e.g. carrier string!
).
tip
If you prefer learning by example, you can check the source Comlink profile for all the published capabilities in the catalog. Choose a capability and click "raw" to see the code.
Specify Safety of the Use-Case
The use-case can be marked as safe
, unsafe
or idempotent
. If the safety is not specified, the use-case is treated as unsafe
by default.
safe
: The use-case doesn't change anything or doesn't perform any action. Generally reading operations can be considered safe, for example retrieving information about shipment or geocoding a postal address.unsafe
: The use-case changes the world state and its retry may result in unintended side effects. For example, sending an email, or placing an order is unsafe: executing these use-cases repeatedly results in sending multiple emails or placing multiple orders.idempotent
: The use-case can be executed multiple times without changing the result. For example updating an article with the same data multiple times results in the same article.
HTTP Methods
If you are familiar with REST APIs and HTTP methods, you can think of the safety in this manner:
safe
corresponds toGET
andHEAD
methods,unsafe
corresponds toPOST
method,idempotent
corresponds toPUT
andDELETE
methods.
For more information see Understanding Idempotency and Safety in API Design.
The safety is defined after the use-case's name:
usecase ShipmentInfo safe {}
usecase SendMessage unsafe {}
usecase UpdateProfile idempotent {}
// if safety is not specified, the use-case is considered unsafe
usecase SendEmail {}
While the safety information is optional, it can be used by OneSDK to treat the use-case in particular manner. For example, the SDK can attempt to automatically repeat a failed request if the use-case is safe
.
Define Input Fields
To execute the use-case, you typically need to provide some input. For example to send a text message, you need at least a recipient's phone number and the message's contents.
In Comlink profile, the use-case's input is specified in the input
block:
usecase ShipmentInfo safe {
input {
trackingNumber
carrier
}
}
The above use-case expects an object with two optional, untyped input fields: trackingNumber
and carrier
. You may want to mark the field as required or specify that carrier
must be a string - see the More About Fields section for more information about these features.
If the use-case doesn't need any input, the input
block can be omitted.
Define Result Fields
Similar to defining the input, use-case can describe its output (called result). Our Shipment info use-case returns a result object with multiple fields:
usecase ShipmentInfo safe {
// ...
result {
carrier
status
origin
destination
events [{
timestamp
statusText
}]
estimatedDeliveryDate
}
}
In this example, the events
field is an array of objects with the fields timestamp
and statusText
. The fields can also be marked as required and non-null - see More About Fields section.
The result itself can be also a an array or a scalar value:
// result is an array of objects
usecase ShipmentInfoEvents {
result [{
timestamp
statusText
}]
}
// result is a scalar value
usecase ShipmentInfoStatus {
result string
}
Define Error
The use-case can define optional error fields. These will be returned when the use-case execution fails:
usecase ShipmentInfo safe {
// ...
error {
title
detail
}
}
Add Human-Readable Descriptions
The use-case will be consumed by computers, but humans will be the ones integrating the use-case into their code. Use-cases and definitions in the profile can be preceded by descriptions. A description consists of title and body surrounded either by a single double quote "
, or three double quotes """
(triple quotes).
The first description in the profile should explain the overall purpose of the use case. Its title also specifies a human readable name of the profile:
"""
Shipment information
Track your shipment. Get the latest information on your shipment status.
"""
name = "delivery-tracking/shipment-info"
version = "1.0.1"
usecase ShipmentInfo safe {}
Both single quotes and triple quotes description can contain both title and a body of the description. In the single quote variant the title is on the same line as quote:
"Retrieve Shipment Status
Get the current shipment status."
usecase ShipmentInfo safe {}
While triple quotes are separated from the description with new lines:
"""
Retrieve Shipment Status
Get the current shipment status.
"""
usecase ShipmentInfo safe {}
Individual fields can be also documented:
usecase ShipmentInfo safe {
input {
"""
Shipment tracking number
Identifier of shipment
"""
trackingNumber
"Carrier
Shipment carrier identification to narrow down the results"
carrier string!
}
}
Single Quote vs. Triple Quote
Both description formats are functionally equivalent so the choice is up to your preference. Our current practice is to use triple quotes for high-level descriptions (for the profile itself and use-cases), and single quotes for individual fields. Single quotes are also convenient when you want a single-line description (i.e. just a title).
More About Fields
In previous steps we have used fields to define contents of input, result and error in the use-case. Fields can be defined as required and non-nullable, and can specify some particular type.
Field Types
If possible, define the types of fields your use-case accepts or provides in result. The example Shipment Information use-case expects the tracking number and carrier identification as strings:
usecase ShipmentInfo safe {
input {
trackingNumber string
carrier string
}
}
The types can be either scalar or collections. Scalar types are primitive values: string
, number
, boolean
. Collections contain other collections or scalars.
Comlink supports the following collections:
List
- Corresponds to Array in JavaScript or List in Python.
- Uses square brackets, e.g.
[string]
defines a list of strings.
Object
- Acts as a dictionary with strings for keys.
- Corresponds to Object in JavaScript or Dictionary in Python.
- Uses curly brackets, e.g.
{myField number}
defines an object with single field of type number.
Objects are commonly used to define inputs, results, and errors:
usecase UseCaseName {
input {
inputField string
}
result {
field1 string
field2 number
}
}
Lists can also contain objects, for example the following use-case provides a result with a list of objects with fields timestamp
and statusText
:
usecase ShipmentInfoEvents {
result [{
timestamp string
statusText string
}]
}
Required Fields
By default, all fields are optional. Add !
after the field name to mark it as required. This is especially useful for specifying use-case's input to avoid executing the use-case unless all required fields are provided:
usecase ShipmentInfo safe {
input {
// Required field with string
trackingNumber! string
// Required, untyped field
carrier!
}
}
Non-null fields
By default, all field can be null
. To mark them as non-nullable, add !
after the type definition.
In the following example, if carrier
is passed in the input, it must be a string, not null
:
usecase ShipmentInfo safe {
input {
trackingNumber! string!
carrier string!
}
}
warning
Note that marking the field as non-nullable doesn't make it required. To make the field required and non-nullable, you must add !
after both the field's name and type.
In the above use-case the trackingNumber
field is both required, and non-nullable:
usecase UseCaseName {
input {
trackingNumber! string!
}
}