7.3 KiB
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.
To learn more about the CLI and the details for each supported command, see
the CLI Commands document.
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 and 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:
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 and its Event
type
as a parameter. There's possibility to leverage Golang's
Context as the optional parameter in the function contract,
as you can see in the list of supported function signatures:
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,
{
"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:
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:
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.
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
.
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
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.