mirror of https://github.com/knative/func.git
324 lines
9.3 KiB
Markdown
324 lines
9.3 KiB
Markdown
# Quarkus Function Developer's Guide
|
||
|
||
When creating a Quarkus function using the `func` CLI, the project directory
|
||
looks like a typical `maven` project. Both HTTP and Event functions have the same
|
||
template structure.
|
||
|
||
```
|
||
❯ func create -l quarkus fn
|
||
Project path: /home/developer/projects/fn
|
||
Function name: fn
|
||
Runtime: quarkus
|
||
|
||
❯ tree
|
||
fn
|
||
├── func.yaml
|
||
├── mvnw
|
||
├── mvnw.cmd
|
||
├── pom.xml
|
||
├── README.md
|
||
└── src
|
||
├── main
|
||
│ ├── java
|
||
│ │ └── functions
|
||
│ │ ├── Function.java
|
||
│ │ ├── Input.java
|
||
│ │ └── Output.java
|
||
│ └── resources
|
||
│ └── application.properties
|
||
└── test
|
||
└── java
|
||
└── functions
|
||
├── FunctionTest.java
|
||
└── NativeFunctionIT.java
|
||
```
|
||
|
||
Aside from the `func.yaml` file, this looks like the beginning of just about
|
||
any Java Maven project. For now, we will ignore the `func.yaml` file, and just
|
||
say that it is a configuration file that is used when building your project.
|
||
If you're really interested, check out the [reference doc](../reference/func_yaml.md).
|
||
To learn more about the CLI and the details for each supported command, see
|
||
the [CLI Commands document](../reference/commands.txt).
|
||
|
||
## Running the function locally
|
||
|
||
To run a function, you'll first need to build it. This step creates an OCI
|
||
container image that can be run locally on your computer, or on a Kubernetes
|
||
cluster.
|
||
|
||
```
|
||
❯ func build
|
||
```
|
||
You might be asked about docker registry during the build step.
|
||
After the function has been built, it can be run locally.
|
||
|
||
```
|
||
❯ func run
|
||
```
|
||
|
||
Functions can be invoked with a simple HTTP request.
|
||
You can test to see if the function is working by using your browser to visit
|
||
http://localhost:8080.
|
||
|
||
## Deploying the function to a cluster
|
||
|
||
To deploy your function to a Kubernetes cluster, use the `deploy` command.
|
||
|
||
```
|
||
❯ func deploy
|
||
```
|
||
|
||
You can get the URL for your deployed function with the `info` command.
|
||
|
||
```
|
||
❯ func info
|
||
```
|
||
|
||
## Testing a function locally
|
||
|
||
|
||
Quarkus functions can be tested locally on your computer.
|
||
The project contains tests which you can run.
|
||
You do this as you would with any Maven project.
|
||
|
||
```
|
||
❯ mvn test
|
||
```
|
||
|
||
## Function reference
|
||
|
||
Boson Quarkus functions have very few restrictions. You can work with it as with any Java Maven project.
|
||
The only real requirements is that your project contain a method annotated with `@Funq`.
|
||
In this section, we will look in a little more detail at how Boson functions are invoked,
|
||
and what APIs are available to you as a developer.
|
||
|
||
### Invocation parameters
|
||
|
||
When using the `func` CLI to create a function project, you may choose to generate a project
|
||
that responds to a `CloudEvent` or simple HTTP. `CloudEvents` in Knative are transported over
|
||
HTTP as a `POST` request, so in many ways, the two types of functions are very much the same.
|
||
They each will listen and respond to incoming HTTP events.
|
||
|
||
When an incoming request is received, your function will be invoked with an instance of type of your choice, see [Types](#types).
|
||
|
||
The instance will contain one of:
|
||
* `data` of `CloudEvent`
|
||
* Body of HTTP POST request
|
||
* Query parameters of HTTP GET request
|
||
|
||
For example, imagine a function that is meant to receive and process purchase data.
|
||
Your function signature may look like this:
|
||
```java
|
||
public class Functions {
|
||
@Funq
|
||
public void processPurchase(Purchase purchase) {
|
||
// do stuff
|
||
}
|
||
}
|
||
```
|
||
With a `Purchase` bean that looks like this:
|
||
```java
|
||
public class Purchase {
|
||
private long customerId;
|
||
private long productId;
|
||
// getters and setter here
|
||
}
|
||
```
|
||
Expecting data that may be represented in JSON like this:
|
||
```json
|
||
{
|
||
"customerId": "0123456",
|
||
"productId": "6543210"
|
||
}
|
||
```
|
||
Depending on the deployment, this function will be invoked in one of three ways:
|
||
* An incoming `CloudEvent` with a JSON object such as above in its `data` property.
|
||
* An ordinary HTTP POST with a JSON object such as above in the body of the request
|
||
* An ordinary HTTP GET with a query string like `?customerId=0123456&productId=6543210`
|
||
|
||
### Return Values
|
||
Functions may return an instance of type satisfying condition described in [Types](#types).
|
||
|
||
You can also return `Uni<T>`.
|
||
Note that the type parameter of `Uni<T>` must satisfy conditions described in [Types](#types).
|
||
This is useful when the function calls asynchronous APIs (e.g. Vert.x HTTP client).
|
||
|
||
The object you return will be serialized in the same format as the object you received.
|
||
|
||
If the function received `CloudEvent` in binary encoding,
|
||
then the object you return will be sent in the `data` property of a binary encoded `CloudEvent`.
|
||
|
||
If the function received vanilla HTTP,
|
||
then the object you return will be sent as the HTTP response body. In the example below, an invocation of this function
|
||
through an incoming `CloudEvent`, will receive a response with a `CloudEvent` containing a list of purchases as its
|
||
`data` property. If the invocation was via an ordinary HTTP request, the response will contain the same list of purchases
|
||
in the HTTP response body, but no `CloudEvent` headers will be included.
|
||
|
||
#### Example
|
||
```java
|
||
public class Functions {
|
||
@Funq
|
||
public List<Purchase> getPurchasesByName(String name);
|
||
}
|
||
```
|
||
|
||
### Types
|
||
|
||
The input and output types of a function can be:
|
||
* `void`
|
||
* `String`,
|
||
* `byte[]`
|
||
* Primitive types and their wrappers (e.g. `int`, `Double`),
|
||
* JavaBeans (the attributes of the bean must also follow the rules defined here)
|
||
* Map, List or Array of the above
|
||
|
||
In addition to above, there is one special type: `CloudEvents<T>` described below [CloudEvent attributes](#cloudevent-attributes).
|
||
|
||
#### Example
|
||
```java
|
||
public class Functions {
|
||
public List<Integer> getIds();
|
||
public Purchase[] getPurchasesByName(String name);
|
||
public String getNameById(int id);
|
||
public Map<String,Integer> getNameIdMapping();
|
||
public void processImage(byte[] img);
|
||
}
|
||
```
|
||
|
||
### CloudEvent attributes
|
||
|
||
Above we described cases where we handle just the `data` portion of a `CloudEvent`.
|
||
In many cases this is all we need.
|
||
However sometimes you may need to also read or write the attributes of a `CloudEvent`, such as `type` or `subject`.
|
||
|
||
For this purpose `Funqy` offers the generic interface `CloudEvent<T>` and a builder, `CloudEventBuilder`.
|
||
Note that the type parameter of `CloudEvent<T>` must satisfy the conditions described in [Types](#types).
|
||
|
||
#### Example
|
||
```java
|
||
public class Functions {
|
||
|
||
private boolean _processPurchase(Purchase purchase) {
|
||
// do stuff
|
||
}
|
||
|
||
public CloudEvent<Void> processPurchase(CloudEvent<Purchase> purchaseEvent) {
|
||
System.out.println("subject is: ", purchaseEvent.subject());
|
||
|
||
if (!_processPurchase(purchaseEvent.data())) {
|
||
return CloudEventBuilder.create()
|
||
.type("purchase.error")
|
||
.build();
|
||
}
|
||
return CloudEventBuilder.create()
|
||
.type("purchase.success")
|
||
.build();
|
||
}
|
||
}
|
||
```
|
||
|
||
### Invocation examples
|
||
|
||
Sample code:
|
||
|
||
```java
|
||
import io.quarkus.funqy.Funq;
|
||
import io.quarkus.funqy.knative.events.CloudEvent;
|
||
|
||
public class Input {
|
||
private String message;
|
||
|
||
// getters and setters...
|
||
}
|
||
|
||
public class Output {
|
||
private String message;
|
||
|
||
// getters and setters...
|
||
}
|
||
|
||
public class Functions {
|
||
@Funq
|
||
public Output withBeans(Input in) {
|
||
// code here
|
||
}
|
||
|
||
@Funq
|
||
public CloudEvent<Output> withCloudEvent(CloudEvent<Input> in) {
|
||
// code here
|
||
}
|
||
|
||
@Funq
|
||
void withBinary(byte[] in) {
|
||
// code here
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
The `Functions::withBeans` function above can be invoked by:
|
||
|
||
* Simple HTTP POST with JSON body
|
||
```shell
|
||
curl "http://localhost:8080/" -X POST \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"message": "Hello there."}'
|
||
```
|
||
|
||
* Simple HTTP GET with query parameters
|
||
```shell
|
||
curl "http://localhost:8080?message=Hello%20there." -X GET
|
||
```
|
||
|
||
* CloudEvent in binary encoding:
|
||
```shell
|
||
curl "http://localhost:8080/" -X POST \
|
||
-H "Content-Type: application/json" \
|
||
-H "Ce-SpecVersion: 1.0" \
|
||
-H "Ce-Type: my-type" \
|
||
-H "Ce-Source: cURL" \
|
||
-H "Ce-Id: 42" \
|
||
-d '{"message": "Hello there."}'
|
||
```
|
||
|
||
* CloudEvent in structured (JSON) encoding:
|
||
```shell
|
||
curl http://localhost:8080/ \
|
||
-H "Content-Type: application/cloudevents+json" \
|
||
-d '{ "data": {"message":"Hello there."},
|
||
"datacontenttype": "application/json",
|
||
"id": "42",
|
||
"source": "curl",
|
||
"type": "my-type",
|
||
"specversion": "1.0"}'
|
||
```
|
||
|
||
The `Functions::withCloudEvent` function can be invoked similarly to `Functions::withBeans`,
|
||
however only the last two examples are correct, the first two (raw HTTP) would result in error.
|
||
|
||
The `Functions::withBinary` function can be invoked by:
|
||
|
||
* CloudEvent in binary encoding:
|
||
```shell
|
||
curl "http://localhost:8080/" -X POST \
|
||
-H "Content-Type: application/octet-stream" \
|
||
-H "Ce-SpecVersion: 1.0"\
|
||
-H "Ce-Type: my-type" \
|
||
-H "Ce-Source: cURL" \
|
||
-H "Ce-Id: 42" \
|
||
--data-binary '@img.jpg'
|
||
|
||
```
|
||
|
||
* CloudEvent in structured (JSON) encoding:
|
||
```shell
|
||
curl http://localhost:8080/ \
|
||
-H "Content-Type: application/cloudevents+json" \
|
||
-d "{ \"data_base64\": \"$(base64 img.jpg)\",
|
||
\"datacontenttype\": \"application/octet-stream\",
|
||
\"id\": \"42\",
|
||
\"source\": \"curl\",
|
||
\"type\": \"my-type\",
|
||
\"specversion\": \"1.0\"}"
|
||
```
|