# \[Proposal\] Add support for plugins to functions-framework ## Motivation We need to add observability capabilities to functions, which facilitate observing and tracking the operation of functions in large-scale scenarios. Referring to [#7](https://github.com/OpenFunction/functions-framework/issues/8), we can take the form of a plugin in functions-framework to wake up the observability component to run at a specific node. For example, in [functions-framework-go](https://github.com/OpenFunction/functions-framework-go), we can add a plugin hook before and after the function is run, and run the logic related to the observability component in the hook. Reference [this](https://github.com/OpenFunction/functions-framework-go/blob/81a7b2951b8af0897978dcc483c1217ac98f02fb/functionframeworks/frameworks.go#L133): ```go func registerHTTPFunction(path string, fn func(http.ResponseWriter, *http.Request), h *http.ServeMux) error { h.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { defer recoverPanicHTTP(w, "Function panic") // execute pre-run plugins fn(w, r) // execute post-run plugins }) return nil } ``` We should also consider as much as possible the consistency of the scheme's implementation in different languages. ## Associated issueses https://github.com/OpenFunction/OpenFunction/issues/146 https://github.com/OpenFunction/functions-framework/issues/8 https://github.com/OpenFunction/functions-framework/issues/9 ## Goals 1. design a common interface in functions-framework-go to support the tracing capabilities of SkyAPM/Go2Sky and OpenTelemetry 2. improve functions-framework-nodejs (to support observability capabilities) 3. improve builder (rewrite the builder to make it better support functions-framework) ## Proposal I'll use `function-framework-go` as an example to illustrate how functions-framework works in this proposal: 1. Directory structure ``` \- context \- context.go \- functionframeworks \- knative \- knative.go \- async \- aysnc.go \- frameworks.go \- plugin \- plugin-a \- plugin-a.go \- plugin-b \- plugin-b.go \- interface.go ``` **context**: Contains logic for handling OpenFunction Context related matters. **functionframeworks**: Contains logic for handling function conversions on different serving runtimes. **plugin**: Contains logic for handling plugins. 2. context Responsible for maintaining the OpenFunction Context structure, which provides the following functions: - Parse the OpenFunction Context - Dock to the function input source and direct the input data to the function - Dock to the function output target and direct the function data to the output target - Encapsulate the return values of function - Provide an interface to call plugins 3. frameworks Responsible for handling function conversion logic on different runtimes, which provides the following functions: - Depending on the serving runtime currently supported by OpenFunction, there are two implementations, `knative` and `async` - Register functions and associate user functions to listening service - Fetch the plugins from OpenFunction Context and call the plugin interface to execute the plugins - Start the listening service 4. plugin Responsible for handling plugins, which provides the following functions: - Register plugins - Execute function pre-run plugins - Execute function post-run plugins Example: Demonstrating the logic of the function conversion section in knative runtime: ```go // Uniform function signature as func(*ofctx.OpenFunctionContext, []byte) ofctx.RetValue func (r *Runtime) RegisterFunction(ctx context.Context, fn func(*ofctx.OpenFunctionContext, []byte) ofctx.RetValue) error { // Register the handler function with the path r.pattern r.handler.HandleFunc(r.pattern, func(w http.ResponseWriter, r *http.Request) { defer functionframeworks.RecoverPanicHTTP(w, "Function panic") // Parse the OpenFunction Context and return a Context instance c, err := functionframeworks.ParseOpenFunctionContext() ... // Get the content of the http request and return data of type []byte as function input in, err := c.GetInput(r) ... // Fetch the list of plugins from the Context and execute the PrePhase stage plugins here r.ProcessPlugins(ctx) ... // Execute the user function and get the return value ret := fn(c, in) // Fetch the list of plugins from the Context and execute the PostPhase stage plugins here err := r.ProcessPlugins(ctx, ofctx.GetPlugins(ofctx.PostPhase)) // Write the return value of the function to the http response w.Write(ret.Data) }) return nil } func (r *Runtime) ProcessPlugins(ctx context.Context, plugins []plugin.Interface) error { for _, plg := range plugins { if err := plg.Exec(ctx); err != nil { return err } } return nil } // Start the server func (r *Runtime) Start(ctx context.Context, ofctx *ofctx.OpenFunctionContext) error { log.Info("Knative Function serving http: listening on port %s", r.port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", r.port), r.handler)) return nil } ```