Одна из самых недооцененных функций, представленных с версии Go 1.8, - это пакет плагинов Go. Плагины - это один из многих архитектурных проектов программного обеспечения, которые позволяют создавать слабосвязанные и модульные программы. В Go плагины пишутся и компилируются отдельно как общие объекты (.so) или библиотеки и могут загружаться динамически во время выполнения.

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

Плюсы

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

Минусы

  • Среда для создания плагина Go, такая как ОС, языковая версия Go и версии зависимостей, должна точно совпадать.
  • На данный момент выгрузка плагинов не разрешена без перезапуска программы.
  • Вы не можете заменить плагин более новой версией во время выполнения; это потому, что Go в настоящее время не поддерживает выгрузку плагинов.
  • Начиная с Go v1.11, вы можете создавать плагины только для Linux, FreeBSD и Mac.

Пример программы: калькулятор доставки

Чтобы продемонстрировать, как разрабатывать плагины на Go, мы создадим минимум и, по общему признанию, непрактичный пример калькулятора доставки. Этот пример, хотя и непрактичный, будет полезен, чтобы понять, как плагины работают в Go.

Базовый калькулятор доставки предоставит вам тарифы в зависимости от того, какой способ доставки и вес посылки вы предоставите. Вы можете поддерживать различные способы доставки, добавляя новые плагины, и калькулятор будет определять ставки и валюту в зависимости от вашего предпочтительного способа доставки. Интерфейс для способа доставки содержит три функции: GetCurrency, CalculateRate и Name.

Приступим к кодированию!

Среда разработки и пакеты

Создайте и инициализируйте проект с помощью модулей Go

mkdir go-plugins-shipping-calculator
cd go-plugins-shipping-calculator
go mod init go-plugins-shipping-calculator
go get github.com/olekukonko/[email protected]

Основная точка входа в приложение

package main
import (
	"fmt"
	"github.com/olekukonko/tablewriter"
	"log"
	"os"
	"plugin"
	"strconv"
)
type Shipper interface {
	Name() string
	Currency() string
	CalculateRate(weight float32) float32
}
func main() {
	args := os.Args[1:]
	if len(args) == 2 {
		pluginName := args[0]
		weight, _ := strconv.ParseFloat(args[1], 32)
		// Load the plugin
		// 1. Search the plugins directory for a file with the same name as the pluginName
		// that was passed in as an argument and attempt to load the shared object file.
		plug, err := plugin.Open(fmt.Sprintf("plugins/%s.so", pluginName))
		if err != nil {
			log.Fatal(err)
		}
		// 2. Look for an exported symbol such as a function or variable
		// in our case we expect that every plugin will have exported a single struct
		// that implements the Shipper interface with the name "Shipper"
		shipperSymbol, err := plug.Lookup("Shipper")
		if err != nil {
			log.Fatal(err)
		}
		// 3. Attempt to cast the symbol to the Shipper
		// this will allow us to call the methods on the plugins if the plugin
		// implemented the required methods or fail if it does not implement it.
		var shipper Shipper
		shipper, ok := shipperSymbol.(Shipper)
		if !ok {
			log.Fatal("Invalid shipper type")
		}
		// 4. If everything is ok from the previous assertions, then we can proceed
		// with calling the methods on our shipper interface object
		rate := shipper.CalculateRate(float32(weight))
		rate1Day := fmt.Sprintf("%.2f %s", rate, shipper.Currency())
		rate2Days := fmt.Sprintf("%.2f %s",
			rate - (rate * .20),
			shipper.Currency())
		rate7Days := fmt.Sprintf("%.2f %s",
			rate - (rate * .70),
			shipper.Currency())
		table := tablewriter.NewWriter(os.Stdout)
		fmt.Println(shipper.Name())
		table.SetHeader([]string{"Number of Days", "Rate"})
		table.Append([]string{"1 Day Express", rate1Day})
		table.Append([]string{"2 Days Shipping", rate2Days})
		table.Append([]string{"7 Days Shipping", rate7Days})
		table.Render()
	}
}

Плагины

Реализация FedEx грузоотправителя

// file: fedex/fedex.go
package main
type shipper struct {}
func (s shipper) Name() string {
	return "Federal Express (Fedex)"
}
func (s shipper) Currency() string {
	return "USD"
}
func (s shipper) CalculateRate(weight float32) float32 {
	cost := weight * 1.8
	tax := cost * .10
	return cost + tax
}
var Shipper shipper

Реализация отправителя королевской почты

# file: royalmail/royalmail.go
package main
type shipper struct {}
func (s shipper) Name() string {
	return "Royal Mail (RM)"
}
func (s shipper) Currency() string {
	return "GBP"
}
func (s shipper) CalculateRate(weight float32) float32 {
	cost := weight * .9
	tax := cost * .5
	return cost + tax
}
var Shipper shipper

Спасибо за чтение и удачного кодирования!

Исходный код: https://github.com/ManiMuridi/go-plugins-shipping-calculator