SoFunction
Updated on 2025-03-05

Problems with the registration and loading mechanism of Golang library plug-in

I recently saw the plug-in loading mechanism of an internal project, which is very good. Of course, the plug-in mentioned here does not refer to the loading mechanism of golang native to the specified so file that can load the buildmode. It is a "plug-in" in software design. If your software is a framework or a platform product, if you want to improve scalability, you can allow third parties to develop third-party libraries and finally assemble these libraries like building blocks. Then this library loading mechanism may be required.

What are our goals? A certain library specification is implemented for third-party libraries. As long as it is developed according to this library specification, the library can be loaded into the framework.

Let's first define the data structure of a plug-in. Here we must use interfaces to standardize it. This can be freely used according to your project. For example, I hope the plug-in has a Setup method to load when it is started. Then I define the following Plugin structure.

type Plugin interface{
  Name() string
  Setup(config map[string]string) error
}

When the framework is started, I started a global variable like this:

var plugins map[string]Plugin

register

Some people may ask, there is a loading function setup here, but why is there no registration logic?

The answer is that the registration logic is placed in the init function of the library.

That is, the framework also provides a registration function.

// package plugin
Register(plugin Plugin)

This register implements the placement of third-party plugins into the plugins global variable.

Therefore, the third-party plugin library is roughly implemented as follows:

package MyPlugin

type MyPlugin struct{
}
func (m *MyPlugin) Setup(config map[string]string) error {
	// TODO
func (m *MyPlugin) Name() string {
	return "myPlugin"
func init() {
	(&MyPlugin)

In this way, the registration logic becomes. If you want to load a plug-in, then you can directly introduce it in the form of _ import.

package main

_ import "/foo/myplugin"
func main() {
}

Overall, the registration of plug-in is "hidden" into the import.

load

The registration logic actually looks ordinary, but the loading logic tests the details.

First of all, there are two points to consider when loading plugins:

  • Configuration
  • rely

Configuration means that the plug-in must have some configuration, which exists as paths in the configuration file yaml.

plugins:
	myplugin:
		foo: bar

Actually, I have reservations about this implementation. The configuration file exists in the form of a configuration item in a file, which seems to be worse than in the form of a configuration file, that is, in the file of config/plugins/.

This will not cause a large configuration file problem. After all, each configuration file itself is a DSL language. If you make the logic of the configuration file complicated, there will be many accompanying bugs caused by configuration file errors.

The second one is dependency. Plugin A depends on plugin B, so here is the order of loading function Setup. If this order of order is purely dependent on the user's "experience", it is very painful to place the Setup call of a certain plug-in before the Setup call of a certain plug-in. (Although there must be a way to do it). A better approach is to rely on the framework's own loading mechanism to load.

First, we define an interface in the plugin package:

type Depend interface{
	DependOn() []string
}

If my plugin relies on a plugin named "fooPlugin", then my plugin MyPlugin implements this interface.

package MyPlugin

type MyPlugin struct{
}
func (m *MyPlugin) Setup(config map[string]string) error {
	// TODO
func (m *MyPlugin) Name() string {
	return "myPlugin"
func init() {
	(&MyPlugin)
func (m *MyPlugin) DependOn() []string {
	return []string{"fooPlugin"}

When we finally load all plugins, we do not simply call Setup by calling all plugins, but use a channel, put all plugins in the channel, and then call Setup one by one. When we encounter other plugins with Depend and the dependency plugins have not been loaded, we place the current plugin at the end of the queue (re-stuck into the channel).

var setupStatus map[string]bool

// Get all registration pluginsfunc loadPlugins() (plugin chan Plugin, setupStatus map[string]bool) {
	// Here is a queue of length 10	var sortPlugin = make(chan Plugin, 10)
	var setupStatus = make[string]bool
	
	// All plugins	for name, plugin := range plugins {
		sortPlugin <- plugin
		setupStatus[name] = false
	}
	return sortPlugin, setupStatus
}
// Load all pluginsfunc SetupPlugins(pluginChan chan Plugin, setupStatus map[string]bool) error {
	num := len(pluginChan)
	for num > 0 {
		plugin <- pluginChan
		
		canSetup := true
		if deps, ok := p.(Depend); ok {
			depends := ()
			for _, dependName := range depends{
				if _, setuped := setupStatus[dependName]; !setup {
						// There are unloaded plugins						canSetup = false
						break
				}
			}
		}
		// If this plugin can be set up		if canSetup {
			(xxx)
			setupStatus[()] = true
		} else {
			// If the plugin cannot be setup, the plugin will be stuffed into the last queue			pluginChan <- plugin
	return nil
} 

The most exquisite thing about the above code is that it uses a buffer channel as a queue. SetupPlugins, the consumption queue side, is setupPlugins. In addition to the consumption queue, it is possible to produce data to the queue, which ensures that all plugins in the queue are loaded in order according to the tagged dependencies.

Summarize

The registration and loading mechanism of this plugin is very elegant. In terms of registration, cleverly use implicit import to register plug-in. In terms of loading, the buffer channel is cleverly used as the loading queue.

This is the article about the registration and loading mechanism of Golang library plug-in. For more related content of Golang plug-in mechanism, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!