gRPC Compiled Plugins
Ambient also supports interacting with plugins over gRPC using the HashiCorp plugin system. This allows you to compile plugins and then send data back and forth just like a standard plugin. A few reasons why you may choose to go this route:
- to dynamically load and unload plugins without restarting the application
- to write a plugin for Ambient in another language other than Go (it will require some non-trivial work to re-implement the plugin API)
- to isolate your application so plugins cannot affect your core application if they panic or leak memory
- to sell a plugin for Ambient, but don't want to distribute the source code
Plugin​
To enable gRPC for your plugin, simply add a file to your plugin directory (cmd/plugin/main.go
) and then update the information below with your plugin information. You must then build the binary by running: go build
.
package main
import (
"os"
"github.com/ambientkit/ambient/pkg/grpcp"
"github.com/username/yourplugin"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
)
func main() {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: grpcp.Handshake,
Plugins: map[string]plugin.Plugin{
"yourplugin": &grpcp.GenericPlugin{Impl: yourplugin.New()},
},
Logger: hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
Output: os.Stderr,
JSONFormat: true,
}),
GRPCServer: plugin.DefaultGRPCServer,
})
}
Ambient Usage​
To reference the gRPC plugin in Ambient, add the plugin name and the path to the binary to the Plugins
array. Currently, gRPC plugins can only be used for generic and middleware plugins, they can't be used for the other core plugins.
plugins := &ambient.PluginLoader{
// Core plugins are implicitly trusted.
Router: awayrouter.New(h),
TemplateEngine: htmlengine.New(),
SessionManager: sessPlugin,
// Trusted plugins are those that are typically needed to boot so they
// will be enabled and given full access.
TrustedPlugins: trusted,
Plugins: []ambient.Plugin{
ambient.NewGRPCPlugin("yourplugin", "./yourplugin/cmd/plugin/yourplugin"),
},
Middleware: []ambient.MiddlewarePlugin{
// Middleware - executes top to bottom.
sessPlugin,
ambient.NewGRPCPlugin("yourmwplugin", "./yourmwplugin/cmd/plugin/yourmwplugin"),
},
}
Limitations​
There are a few limitations when using gRPC plugins because of the nature of the communication. You will need to keep these use cases in mind:
- storing values in request context: if you are relying on
context
fromhttp.Request
in your middleware and routes to store and read values, it will be restricted to just your plugin. There is no way to serialize context values because you can't iterate over them so they won't pass between the server and plugin. The context is stored in a map from either the middleware or routes and then it's accessible by routes and funcmaps, respectively. - using non-primitives when passing data into templates: if you try to pass in a template.HTML via vars, it will be converted to string after serialization/deserialization via gRPC since gRPC only supports primitives. The suggested approach is to use a FuncMap instead that returns escaped HTML (
template.HTML
). Reference. - passing data into templates will be run through marshal/unmarshal in JSON: if you don't use JSON tags and then use capital letters, gRPC will respect the capitalization. If you do use JSON tags, then gRPC will use the JSON tag as the name during serialization/deserialization. We force this even when using standard plugins so there is consistency between standard plugins and gRPC plugins. You can read more about here in the Template Engine section.
If you want to take a closer look at the definitions, all of the data that is transferred between the server and the plugins is documented in these .proto
files.
Testing​
There is a test suite that is shared between standard and gRPC plugins available in the plugin repository. It's separate from the ambient
repository because it requires real plugins to do the testing so to limit dependencies back and forth, we separated them for now. If you find there are inconsistencies between when a plugin is accessed using the standard method vs the gRPC method, please let us know.
Benchmarks​
To give you a sense of the performance of standard vs gRPC plugins, we ran tests against one of the endpoints that hit most of the components (router, template engine, secure site, assets, and funcmap). Google recommends you aim for a response time of less than 200ms, but less than 100ms is better - keep in mind these are all local tests so they will be quicker than a test across a network, but the purpose is to show the variance in response times. Even when running these sets of tests multiple times, there will be variance between them.
Based on the results below, gRPC is about 3 times slower than standard plugins, but it should not be significant enough for users to notice.
Non-Concurrent Response Times​
These are the results for 5000 tests run one after another:
Standard Plugins: avg: 0.0268ms | max: 2ms | min: 0ms
gRPC Plugins: avg: 3.035ms | max: 6ms | min: 3ms
Concurrent Response Times​
These are the results for 5000 tests (50 concurrently for 100 tests):
Standard Plugins: avg: 6.0218ms | max: 10ms | min: 0ms
gRPC Plugins: avg: 19.4602ms | max: 28ms | min: 8ms