Working with Go plugins
Go plugin are a pretty hidden feature; It’s not that there is NO documentation, but there is very little of it. The reason is probably that this feature is not one of go’s strong suits.
The plugin package works on Linux systems and alike, hence, any example that you see for loading a plugin uses the .so extension.
Known issues with go plugins
- They are annoying to compile. While go programs are easy to build with go install, there is no such thing for go plugins. Not only that, if you have multiple files, they must reside in the same library, and all must be included in the build command.
- No package support! All of you files must be in the same package. Giving the fact that this is a plugin and not a whole application, that might not be the worst thing.
All of these annoyances cause developers to seek other solutions, mostly RPCs. Now, calling RPC a plugin is an issue by itself, but I won’t go into that one. RPC is a code called on another process. The plus side is that is more resilient – if something happens to the plugin your process still works, but the speed penalty is severe.
|RPC plugin||in proc go plugin|
|Can be written in any programming language||Can only be written in Go|
|Limited to the language capabilities||No packages, every file must be named|
|Not a language feature, but there are well tested open source projects||A part of the language|
|Can run on another machine; great for transferring high CPU loads||Runs on the same machine|
|Slow Marshaling||No need for marshaling|
Both approaches have their merits – but go limits you, and that’s not great. In other languages you can have libraries and plugins, and it’s up to you what to choose. Here, you’re also limited by the scope of the project – a serious problem. Bypasses do exists, I’ll might refer to them in another post, however, I don’t like to be forced to resort to bypasses.
Let’s get to work
In order for us to be able to call methods on the object that we pull, a protocol needs to be put in place between the caller and the plugin, not surprising, that protocol is an interface. The Interface is only implemented on the caller side – the implementer doesn’t need to know about it – a usual thing in go. For our example we will skew from the “hello world” translation example, to something a little bit more realistic.
Our program will be constitute from a simple loop that logs 10 messages. The main program is obviously not the issue, but the logger is, as it represents the plugin pattern.
Coarse of execution
- The app starts, and calls the get logger function
- The function return the default logger if no program parameters are provided
- If a parameter is provided, the function looks for an existing plugin with that name
- If this plug in exists, we initialize and return it. If not, we return nil.
- The program is now using the logger, or, reporting that there is none.
Pulling an interface from a plugin
- We first define the plugin interface in the main program.
- We define a struct in the plugin that adhere to that interface.
- A twist: We must create a var of the type of the struct we created. Even if the struct is exportable, without a var, it won’t work. We are using an actual instance, and not instantiating one ourselves.
- We make sure that we got the right interface
- We use the interface.