func/docs/function-templates/golang.md

7.3 KiB
Raw Permalink Blame History

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.