9 октября 2018 г.
Обзор
Команда Go недавнообъявилиспользуется дляоткрытое облакоРазработка переносимых облачных API и инструментов, проект с открытым исходным кодомGo Cloud. В этом посте подробно рассказывается о Wire, инструменте внедрения зависимостей, который поставляется с Go Cloud.
Какую проблему решает Wire?
внедрение зависимости— это стандартный метод написания масштабируемого кода с низким уровнем взаимозависимости. Потому что Dependency Injection явно предоставляет компонентам все зависимости, необходимые им для работы. В Go это обычно принимает форму передачи зависимости конструктору:
// NewUserStore返回一个使用cfg和db作为依赖项的UserStore。
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
Этот метод хорошо работает в небольших масштабах, но более крупные приложения будут иметь сложный граф зависимостей. Это приводит к куску кода инициализации, зависящему от порядка, что не очень весело. Поскольку некоторые зависимости используются несколько раз, часто трудно четко разделить этот код. Замена одной реализации службы другой также может быть болезненной, поскольку она включает в себя изменение графа зависимостей путем добавления совершенно нового набора зависимостей (и их зависимостей...) и удаления старых, которые не используются. На практике изменение кода инициализации в приложениях с огромными графами зависимостей утомительно и медленно.
Инструменты внедрения зависимостей, такие как Wire, предназначены для упрощения управления кодом инициализации. Вы можете описать свои службы и их зависимости в виде кода или конфигурации, а Wire обрабатывает создание графа, который определяет порядок инициализации и способ доставки зависимостей, необходимых для каждой службы. Измените зависимости вашего приложения, изменив сигнатуры функций, добавив или удалив инициализаторы и позволив Wire выполнить утомительную работу по созданию кода инициализации для всего графа зависимостей.
Почему это часть Go Cloud?
Цель Go Cloud — упростить написание переносимых облачных приложений, предоставляя идиоматические API-интерфейсы Go для подходящих облачных сервисов. Например,blob.BucketПредоставляет API хранилища с реализациями для Amazon S3 и Google Cloud Storage (GCS).blob.Bucket
Написанные приложения могут менять реализации без изменения логики приложения. Однако код инициализации по своей сути зависит от поставщика, и у каждого поставщика есть свой набор зависимостей.
Например,Создать GCSblob.Bucket
необходимостьgcp.HTTPClient
, что в итоге требуетgoogle.Credentials
,иСоздайте один для S3тогда нужноaws.Config
, что в конечном итоге требует учетных данных AWS. Поэтому обновите приложение, чтобы использовать другойblob.Bucket
Реализация включает в себя утомительное обновление графа зависимостей, которое мы описали выше. Основным вариантом использования Wire является облегчение обмена реализациями портативного API Go Cloud, но в то же время он также является универсальным инструментом для внедрения зависимостей.
Разве эта работа еще не сделана?
На самом деле существует множество фреймворков внедрения зависимостей. Для Го,раскопки UberиФейсбук инжектОба используют отражение для внедрения зависимостей во время выполнения. Основное вдохновение Wire исходит от JavaDagger 2и использовать генерацию кода вместо отражения илиПоиск сервисов.
Мы считаем, что у такого подхода есть несколько преимуществ:
- Когда граф зависимостей становится сложным, внедрение зависимостей во время выполнения становится трудно отслеживать и отлаживать. Использование генерации кода означает, что код инициализации, выполняемый во время выполнения, представляет собой обычный идиоматический код Go, который легко понять и отладить. Он не станет дерганным и сложным для понимания из-за различных хитростей и хитростей фреймворка. Особенно важно, чтобы такие проблемы, как забывание зависимостей, становились ошибками времени компиляции, а не времени выполнения.
- иПоиск сервисовДругое, не нужно заморачиваться придумыванием имен для регистрации сервисов. Wire использует типы в синтаксисе Go для соединения компонентов с их зависимостями.
- Легче предотвратить раздувание зависимостей. Код, сгенерированный Wire, будет импортировать только те зависимости, которые вам нужны, поэтому в вашем двоичном файле не будет неиспользуемых импортов. Инжектор зависимостей среды выполнения не может идентифицировать неиспользуемые зависимости, пока они не будут запущены.
- Граф зависимостей Wire известен статически, что предоставляет возможности для инструментов и визуализации.
Как это работает?
Wire имеет две основные концепции: провайдеры и инжекторы.
провайдер— это обычные функции Go, которые «поставляют» значения в соответствии со своими зависимостями, которые просто описываются как аргументы функции. Вот пример кода, который определяет трех провайдеров:
// NewUserStore与我们上面看到的功能相同; 它是UserStore的提供者,
//依赖于*Config和*mysql.DB。
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
// NewDefaultConfig是*Config的提供者,没有依赖。
func NewDefaultConfig() *Config {...}
// NewDB是基于某些连接信息的* mysql.DB的提供者。
func NewDB(info *ConnectionInfo) (*mysql.DB, error) {...}
обычно используется вместеProviderSets
можно сгруппировать вProviderSets
. Например, при создании*UserStore
использовать значение по умолчанию*Config
очень распространено, поэтому мы можемProviderSet
средняя параNewUserStore
иNewDefaultConfig
Чтобы сгруппировать:
var UserStoreSet = wire.ProviderSet(NewUserStore, NewDefaultConfig)
инжекторявляются сгенерированными функциями, которые вызывают провайдеров в порядке, требуемом их зависимостями. Запишите подпись инжектора, включая все необходимые входные данные в качестве параметров, и вставьтеwire.Build
вызов,wire.Build
Содержит список поставщиков или наборов поставщиков, необходимых для построения конечного результата:
func initUserStore() (*UserStore, error) {
//我们将得到一个错误,因为NewDB需要一个*ConnectionInfo
//我们没有提供。
wire.Build(UserStoreSet, NewDB)
return nil, nil // 这些返回值会被忽略。
}
Теперь мы запускаем go generate, чтобы выполнить wire:
$ go generate
wire.go:2:10: inject initUserStore: no provider found for ConnectionInfo (required by provider of *mysql.DB)
wire: generate failed
Ой! мы не включалиConnectionInfo
Он также не говорит Wire, как его построить. Wire с пользой сообщает нам номера и типы задействованных линий. Мы можем добавить его провайдера вwire.Build
или добавьте его в качестве параметра:
func initUserStore(info ConnectionInfo) (*UserStore, error) {
wire.Build(UserStoreSet, NewDB)
return nil, nil // 这些返回值会被忽略。
}
в настоящее времяgo generate
Будет создан новый файл со сгенерированным кодом:
// File: wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
func initUserStore(info ConnectionInfo)(* UserStore,error){
defaultConfig:= NewDefaultConfig()
db,err:= NewDB(info)
if err!= nil {
return nil, err
}
userStore,err:= NewUserStore(defaultConfig,db)
if err!= nil {
return nil, err
}
return userStore,nil
}
Любые объявления, не относящиеся к инжектору, будут скопированы в сгенерированный файл. Во время выполнения нет зависимости от Wire: весь написанный код — это обычный код Go.
Как видите, вывод очень близок к тому, что написали сами разработчики. Это всего лишь простой пример только с тремя компонентами, поэтому написание инициализаторов вручную не слишком болезненно, но Wire экономит много ручной работы для компонентов и приложений с более сложными графами зависимостей.
Как я могу принять участие и узнать больше?
Wire READMEПодробно рассказывается, как использовать Wire и его более продвинутые функции. есть еще одинруководствоПровод можно использовать в простом приложении.
Спасибо за любые комментарии о вашем опыте работы с Wire!Перейти в облакоРазработка происходит на GitHub, поэтому вы можетепридумать вопросПриходите и скажите нам, что может быть лучше. Для получения обновлений проекта и обсуждений, пожалуйста, присоединяйтесьсписок рассылки проекта.
Спасибо, что нашли время, чтобы узнать о Go Cloud Wire. Мы рады сотрудничать с вами, чтобы сделать Go предпочтительным языком для разработчиков, создающих портативные облачные приложения.
Автор: Роберт ван Гент.
оригинал:blog.golang.org/wire