func/docs/function-templates/golang.md

249 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Golang Function Developer's Guide
When creating a Go (Golang) function using the `func` CLI, the project directory
looks like a typical Go project. Both HTTP and Event functions have the same
template structure.
```
func create -l go fn
Project path: /home/developer/projects/fn
Function name: fn
Runtime: go
tree
fn
├── README.md
├── func.yaml
├── go.mod
├── go.sum
├── handle.go
└── handle_test.go
```
Aside from the `func.yaml` file, this looks like the beginning of just about
any Go 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/func.md).
## 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
```
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. You can also access liveness and readiness
endpoints at http://localhost:8080/health/liveness and
http://localhost:8080/health/readiness. These two endpoints are used
by Kubernetes to determine the health of your function. If everything
is good, both of these will return `{"ok":true}`.
## 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
Go functions can be tested locally on your computer. In the project there is
a `handle_test.go` file which contains simple test which can be extended as needed.
Yo can run this test locally as you would do with any Go project.
```
go test
```
## Function reference
Boson Go functions have very few restrictions. You can add any required dependencies
in `go.mod` and you may include additional local Go files. The only real requirement are
that your project is defined in a `function` module and exports the function `Handle()`
(supported contracts of this function will be discussed more deeply later).
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.
#### Function triggered by HTTP request
When an incoming request is received, your function will be invoked with two parameters:
Golang's [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter) and [http.Request](https://golang.org/pkg/net/http/#Request).
Then you can use standard Golang techniques to access the request (eg. read the body)
and set a proper HTTP response of your function, as you can see on the following example:
```go
func Handle(res http.ResponseWriter, req *http.Request) {
// Read body
body, err := ioutil.ReadAll(req.Body)
defer req.Body.Close()
if err != nil {
http.Error(res, err.Error(), 500)
return
}
// Process body & function logic
// ...
}
```
#### Function triggered by CloudEvent
If the incoming request is a `CloudEvent`, the event is provided via
[CloudEvents Golang SDK](https://cloudevents.github.io/sdk-go/) and its `Event` type
as a parameter. There's possibility to leverage Golang's
[Context](https://golang.org/pkg/context/) as the optional parameter in the function contract,
as you can see in the list of supported function signatures:
```go
Handle()
Handle() error
Handle(context.Context)
Handle(context.Context) error
Handle(cloudevents.Event)
Handle(cloudevents.Event) error
Handle(context.Context, cloudevents.Event)
Handle(context.Context, cloudevents.Event) error
Handle(cloudevents.Event) *cloudevents.Event
Handle(cloudevents.Event) (*cloudevents.Event, error)
Handle(context.Context, cloudevents.Event) *cloudevents.Event
Handle(context.Context, cloudevents.Event) (*cloudevents.Event, error)
```
For example, a `CloudEvent` is received which contains a JSON string such as this in its data property,
```json
{
"customerId": "0123456",
"productId": "6543210"
}
```
to access this data, we need to define `Purchase` structure, which maps properties in `CloudEvents`
data and retrieve it from the incoming event:
```go
type Purchase struct {
CustomerId string `json:"customerId"`
ProductId string `json:"productId"`
}
func Handle(ctx context.Context, event cloudevents.Event) err error {
purchase := &Purchase{}
if err = cloudevents.DataAs(purchase); err != nil {
fmt.Fprintf(os.Stderr, "failed to parse incoming CloudEvent %s\n", err)
return
}
// ...
}
```
Or we can use Golang's `encoding/json` package to access the `CloudEvent` directly as
a JSON in form of bytes array:
```golang
func Handle(ctx context.Context, event cloudevents.Event) {
bytes, err := json.Marshal(event)
// ...
}
```
### Return Values
As mentioned above, HTTP triggered functions can set the response directly via
Golang's [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter).
```go
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
// Set response
res.Header().Add("Content-Type", "text/plain")
res.Header().Add("Content-Length", "3")
res.WriteHeader(200)
_, err := fmt.Fprintf(res, "OK\n")
if err != nil {
fmt.Fprintf(os.Stderr, "error or response write: %v", err)
}
}
```
Functions triggered by `CloudEvent` may return nothing, `error` and/or `CloudEvent` in order
to push events into the Knative eventing system. In this case, the developer is required
to set a unique `ID`, proper `Source` and a `Type` of the CloudEvent. The data can be populated
from a defined structure or from a `map`.
```go
func Handle(ctx context.Context, event cloudevents.Event) (resp *cloudevents.Event, err error) {
// ...
response := cloudevents.NewEvent()
response.SetID("example-uuid-32943bac6fea")
response.SetSource("purchase/getter")
response.SetType("purchase")
// Set the data from Purchase type
response.SetData(cloudevents.ApplicationJSON, Purchase{
CustomerId: custId,
ProductId: prodId,
})
// OR set the data directly from map
response.SetData(cloudevents.ApplicationJSON, map[string]string{"customerId": custId, "productId": prodId})
// Validate the response
resp = &response
if err = resp.Validate(); err != nil {
fmt.Printf("invalid event created. %v", err)
}
return
}
```
## Dependencies
Developers are not restricted to the dependencies provided in the template
`go.mod` file. Additional dependencies can be added as they would be in any
other Golang project.
### Example
```console
go get gopkg.in/yaml.v2@v2.4.0
```
When the project is built for deployment, these dependencies will be included
in the resulting runtime container image.