События Golang: EventEmitter / dispatcher для архитектуры плагинов

В Узел.js я смог сделать клон WordPress довольно легко, используя EventEmitter для репликации и создания системы hooks в ядро CMS, к которой Плагины могли бы прикрепиться.

теперь мне нужен этот же уровень расширяемости и изоляции ядра для моей CMS, написанной и портированной. В основном у меня есть ядро, но чтобы сделать его действительно гибким, я должен иметь возможность вставлять события (крючки) и подключать плагины к этим крючкам с дополнительными функциональность.

меня не волнует перекомпиляция (динамическая / статическая компоновка), пока вам не нужно изменять ядро для загрузки плагинов - ядро CMS никогда не должно быть изменено. (например, WP, Drupal и т. д.)

Я заметил, что есть несколько довольно неизвестных проектов, пытающихся реализовать события в Go, выглядящие несколько похожими на EventEmitter в Узел.js:

https://github.com/CHH/eventemitter

https://github.com/chuckpreslar/emission

поскольку эти 2 проекта выше не получили большой популярности и внимания, как-то я чувствую, что этот способ мышления о событиях теперь может быть, как мы должны это сделать в Go? Означает ли это, что Go, возможно, не ориентирован на эту задачу? Чтобы сделать действительно расширяемые приложения через плагины?

не похоже, иди построил событий в его ядро, и RPC не кажется допустимым решением для интеграции плагинов в ваше основное приложение, как они были встроены изначально, и как будто они были частью самого основного приложения.

каков наилучший способ бесшовной интеграции плагинов в ваше основное приложение, для неограниченных точек расширения (в ядре) без манипулирования ядром каждый раз, когда вам нужно подключить новый плагин?

1 ответов


в общем, в Go, если вам нужны события, вам, вероятно, нужно использовать каналы, но если вам нужны плагины, способ пойти межфазные границы. Вот немного длинный пример простой архитектуры плагина, которая минимизирует код, который должен быть написано в главном файле приложения для добавления плагинов (это может быть автоматизировано, но не dnyamic, см. ниже).

Я надеюсь, что это в том направлении, которое вы ищете.


1. Интерфейсы Плагинов

так хорошо, допустим, у нас есть два плагина, Fooer и Doer. Сначала определим их интерфейсы:

// All DoerPlugins can do something when you call that method
type DoerPlugin interface {
    DoSomething() 
}

// All FooerPlugins can Foo() when you want them too
type FooerPlugin interface {
    Foo()
}

2. Реестр Плагинов

теперь наше основное приложение имеет реестр плагинов. Я делаю что-то быстрое и грязное здесь, просто чтобы получить идею:

package plugin_registry

// These are are registered fooers
var Fooers = []FooerPlugin{}

// Thes are our registered doers
var Doers = []DoerPlugin{}

теперь мы выставляем методы для добавления плагинов в реестр. Простой способ-добавить по одному на тип, но вы можете пойдите с более сложным материалом отражения и имейте одну функцию. Но обычно в Go, старайтесь держать вещи простыми:)

package plugin_registry

// Register a FooerPlugin
func  RegisterFooer(f FooerPlugin) {
    Fooers = append(Fooers, f)
}

// Register a DoerPlugin
func RegisterDoer(d DoerPlugin) {
    Doers = append(Doers, d)
}

3. Реализация и регистрация плагина

предположим, что это ваш модуль. Мы создаем плагин, который является исполнителем, и в нашем пакете init() метод мы регистрируем его. init () происходит один раз при запуске программы для каждого импортированного пакета.
package myplugin 

import (
    "github.com/myframework/plugin_registry"
)
type MyPlugin struct {
    //whatever
}

func (m *MyPlugin)DoSomething() {
    fmt.Println("Doing something!")
}

опять же, вот "init magic", который регистрирует пакет автоматически

func init() {
    my := &MyPlugin{}
    plugin_registry.RegisterDoer(my)
}

4. Импорт плагинов регистрирует их автоматически

и теперь, единственное, что нам нужно изменить то, что мы импортируем в наш основной пакет. С Go не имеет динамического импорта или ссылки, это единственное, что вам нужно написать. Это довольно тривиально, чтобы создать go generate скрипт, который будет генерировать основной файл заглянув в дерево файлов или файл конфигурации и найдя все плагины, которые необходимо импортировать. Она не динамична, но ее можно автоматизировать. Поскольку main импортирует плагин для побочного эффекта регистрации, то import использует пустой идентификатор, чтобы избежать неиспользуемой ошибки импорта.

package main

import (
    "github.com/myframework/plugin_registry"

    _ "github.com/d00dzzzzz/myplugin" //importing this will automaticall register the plugin
)

5. В ядре приложения

и теперь наше основное приложение не нуждается в изменении кода, чтобы иметь возможность взаимодействовать с плагинами:

func main() {


    for _, d := range plugin_registry.Doers {
        d.DoSomething()
    }

    for _, f := range plugin_registry.Fooers {
        f.Foo()
    }

}

и это все. Имейте в виду, что реестр плагинов должен быть отдельным пакет что как ядро приложения, так и плагины могут импортировать, поэтому у вас не будет кругового импорта.

конечно, вы можете добавить обработчики событий в этот микс, но, как я продемонстрировал, это не нужно.