Комментарии 92
по-моему #2: у меня складывается впечатление, что все это ради красивого тела main()…
опыт шепчет: чем больше явного, тем лучше…
а для проекта, средней сложности и объема, использование такого подхода приведет к тому, что для фикса мелочи незнакомым человеком, придется просмотреть и вникнуть во все щели кода и логики, абы ничего не упустить и не сломать.
И согласен, лучше, если код более явный, если хорошей «магии» из-за особенностей языка не применить.
Он достаточно умён, чтобы создавать только один экземпляр каждого предоставленного типа
Создаёт только синглтоны?
Если я правильно понимаю со слов автора — DI в dig не совсем то же что IoC контейнер Laravel с его DI. Не в плане реализаций, а в плане для чего можно библиотеку использовать. И разработчики dig уточняют:
Good for:
Powering an application framework, e.g. Fx.
Resolving the object graph during process startup.
Bad for:
Using in place of an application framework, e.g. Fx.
Resolving dependencies after the process has already started.
Exposing to user-land code as a Service Locator.
Т. е. используйте для усиления возможностей фреймфорка либо для запуска приложения. Всё.
В Laravel же куда не глянь — везде контейнер с его внедрением зависимостей.
А на счёт магии… Она тоже разной бывает. И не всегда тёмной.
Данный подход был бы прекрасен, если бы под капотом не использовалась рефлексия, чреватая паниками во время выполнения.
Я все таки предпочитаю явно передавать и создавать зависимости, что может быть многословно, но зато обо всех косяках мне расскажет компилятор при сборке.
Все дело в том, что в Go внедрение зависимостей происходит на этапе инициализации приложения. То есть, с начала «всё подняли», а потом «бежим в рантайм». И если у Вас есть хотя бы 1 E2E-тест, то проблемы с паниками в механизме внедрения полностью пресекаются. И даже более того, поощряются, потому что это нормально хлопнуть приложение полностью на этапе инициализации, а бойлерплейт это уменьшает невообразимо.
Чем больше приложение, тем сильнее это проявляется. Язык Go отлично подходит для создания больших приложений, а библиотека dig — прекрасный инструмент для внедрения зависимостей.
Мне казалось, что как раз потому, что go плохо подходит для написания больших приложений, то внедрение зависимостей предлагается делать через сервисную архитектуру.
Все таки Java головного мозга очень тяжело поддается лечению.
Это просто пример такой. А когда таких "простых и явных" мест будет десятки и изменение в одном месте надо будет дублировать в остальных — вот тут и придет нужна в DI. А еще DI нужен для абстрактных развязок, что бы была возможность гибко настраивать абстракции.
Go создавался как простой до идиотизма язык, где очень тяжело выстрелить себе в ногу. Не надо делать из него Джаву, ничего хорошего не получится.
Нигде, кроме Джавы, DI широко не используются. И ничего, живут как то люди, и неплохо живут.
Не надо гибко настраивать абстракции. Они протекать начнут.
И что вы предлагаете вместо этого, жестко связать два компонента вместе, что бы их можно было менять только вместе? Очень круто.
Почему-то программисты, которые любят рассказывать про "дырявые" абстракции не предлагают альтернативу, так как ее обычно нет.
Под абстракцией я имел ввиду, что если у вас компонент A зависит от компонента B, то его стоит сделать зависимым только от абстрактного компонента asbtract_B, для которого B будет имплементацией. И это стоит делать как минимум для того, что бы разделить эти компоненты и дать возможность менять их независимо. Могу отправить вас почитать "Чистую архитектуру", поверьте, стоит потратить на это немного времени.
Go создавался как простой до идиотизма язык, где очень тяжело выстрелить себе в ногу. Не надо делать из него Джаву, ничего хорошего не получится.
И именно поэтому в комплекте с Go вам предлагают пользоватся микросервисами, что бы прокладывать храницы на уровне API спецификаций. Я нахожу это несколько ироничным, потому что для написания крупных приложений приходится управлять кучей микросервисами с оверхедами на сетевые запросы вместо вызовов функций, и плюсы микросервисной архитектуры не всегда это окупают, даже если получится их сохранить.
Нигде, кроме Джавы, DI широко не используются. И ничего, живут как то люди, и неплохо живут.
Даже в C#? Ой, вряд ли. А так, используется много где, в тех же rails или django, когда вам дают динамический cursor к базе данных — это тоже DI.
Описанный подход лично я считаю вредительским и явным оверинжинирингом.
Вместо того чтобы сделать нормальные абстракции поверх интерфейсов, а ещё лучше вообще их не делать, пока не возникло ВНЕЗАПНО НЕОБХОДИМОСТИ поменять реализацию коннекта к базе или ещё чего-то такого (как по мне, это все равно невозможно сделать таким способом irl)
Посыл то правильный — надо писать нормальный тестируемый код с нормальными абстракциями и разделением ответственности, но реализация просто чудовищная
Вместо того чтобы сделать нормальные абстракции поверх интерфейсов, а ещё лучше вообще их не делать, пока не возникло ВНЕЗАПНО НЕОБХОДИМОСТИ поменять реализацию коннекта к базе или ещё чего-то такого
Золотые слова! Не надо заранее изобретать абстракцию, которая с вероятностью 95% никогда не понадобится!
Как бы, именно в этом проблема. В том, что эти 5%, которые вы надеетесь сделать потом, вы не сделаете.
Если вы завяжетесь на конкретную реализацию, а окажется, что выбор был не удачным — отказаться от нее будет сложно.
И ладно, заменить конкретную реализацию в коде абстрактной, если она хорошо изолирована, но выпилить конкретный компонент или отказаться от технологии, если она пронизывает весь ваш код, а не отсечена абстракцией практически нереально.
Если вы опираетесь на какую-то технологию, то скорее всего:
- Таких классов у вас довольно много
- Код, вместо использования абстракций, которые бы можно было подложить использует особенности технологии, на которую вы завязались.
Поэтому вы прикидываете, что надо будет переписать и перепроверить весь код приложения и грустно вздыхая откладываете это. В той же "чистой архитектуре" есть отличный пример, когда они сильно завязались на SQL диалект одной базы, а потом, когда попытались перейти на другую из-за того, что поддержка первой закончилась — столкнулись с тем, что они не могут это сделать не переписав все приложение. В итоге, бизнес нанял специальных людей, что бы они поддерживали эту БД.
Понятное дело, если у вас, скажем, правильная микросервисная архитектура, то вы можете довольно свободно менять базы данных для приложения, но микросервисная архитектура не для всех, а уж правильная — тем более.
Код, вместо использования абстракций, которые бы можно было подложить использует особенности технологии, на которую вы завязались.Т.е. вместо того чтобы использовать все преимушества технологии, надо ограничить себя абстракцией
когда попытались перейти на другую из-за того, что поддержка первой закончилась — столкнулись с тем, что они не могут это сделать не переписав все приложение.этого можно избежать если делать многослойную архитектуру, т.е. надо переписать только нижнии слой, а где надо исправить покажут тесты
Т.е. вместо того чтобы использовать все преимушества технологии, надо ограничить себя абстракцией
Если вы на 100% уверены, что вам никогда не надо будет сменить эту технологию, то зачем? Например, вроде как почти все программисты стараются на 100% использовать возможности языка программирования. А если же не уверены, то стоит задуматься, потому что, например, те же базы данных очень часто выбираются чисто по инерции.
этого можно избежать если делать многослойную архитектуру, т.е. надо переписать только нижнии слой, а где надо исправить покажут тесты
Так разве в этом случае верхние слои не будут работать с абстракцией как раз?
Если вы на 100% уверены, что вам никогда не надо будет сменить эту технологию, то зачем?Может быть обычно меняют технологию те кто абстрагируются, и не используют все преимушества технологии? Если надо переходить на другую технологию, значит она работает иначе, т.е есть возможности которых нет в абстракции, что тогда делать?
Так разве в этом случае верхние слои не будут работать с абстракцией как раз?Нет, сервис как работал с `ProductsRepository.list` так и работает.
Может быть обычно меняют технологию те кто абстрагируются, и не используют все преимушества технологии? Если надо переходить на другую технологию, значит она работает иначе, т.е есть вожможности которых нет в абстракции, что тогда?
Скорее всего, тут стоит исходит из конкретной причины перехода. Например, если вы мигрируете с базы, в которой нет индекса нужного вам типа на другую, сильно ли это отразится на абстракциях? Мне кажется, не очень, максимум, надо будет добавить какой-нибудь fallback_index_type.
Нет, сервис как работал с ProductsRepository.list
так и работает.
Возможно я не совсем вас понимаю, но разве ProductsRepository.list
не абстракция над тем, где и как у вас реально лежат данные?
Например, если вы мигрируете с базы, в которой нет индекса нужного вам типа на другую, сильно ли это отразится на абстракциях? Мне кажется, не очень, максимум, надо будет добавить какой-нибудь fallback_index_type.Да, но другая база может не работать как старая в других местах, где опять надо вносить изменения, а если работате как старая то и старый код должен работать
Возможно я не совсем вас понимаю, но разве ProductsRepository.list не абстракция над тем, где и как у вас реально лежат данные?list это статический метод в классе ProductsRepository, если для вас это абстракция, ок пусть будет так, но тогда код без абстракции может быть только если у вас один метод
Да, но другая база может не работать как старая в других местах, где опять надо вносить изменения, а если работате как старая то и старый код должен работать
Если у вас нормальная абстракция, то большинства таких проблем получится избежать. Разумеется, если вы меняете одну технологию на кардинальную другую, то написать качественную абстракцию довольно сложно, но для тех же реляционных баз данных абстракции в виде ORM пишутся довольно легко и миграция с, к примеру, mysql на postgres может пройти довольно бесшовно. Но вы же не будете утверждать, что они работают одинаково?
Другой вариант, это когда программный код восполняет недостаток функционала у технологии. Например, в memcached нельзя хранить списки, но ваша абстракция позволяет это делать упаковывая их в memcached неким способом.
но для тех же реляционных баз данных абстракции в виде ORM пишутся довольно легко и миграция с, к примеру, mysql на postgres может пройти довольно бесшовно. Но вы же не будете утверждать, что они работают одинаково?В postgres есть масивы, в mysql нет, т.е. можно использовать либо весь функционал без абстракции, либо урезаный функционал но с абстракциями, на случай когда надо поменять базу, что случается очень редко
что случается очень редко
Это если мы говорим про такие швейцарские ножи как postgres и mysql.
А необходимости менять скажем, одно kv на другое или одну nosql документную базу на другую возникает чаще.
Из того, что случалось со мной за не очень долгую карьеру:
- Надо было заменить чем-то redis, так как у него нет внятного master-master
- Надо уходить от использования rethinkdb, потому что его поддержка уже совсем скурвилась.
В первом случае, абстракция над kv делается довольн легко, второй случай это только pet проект, но и там все получается относительно неплохо.
В той же «чистой архитектуре» есть отличный пример, когда они сильно завязались на SQL диалект одной базы, а потом, когда попытались перейти на другую из-за того, что поддержка первой закончилась — столкнулись с тем, что они не могут это сделать не переписав все приложени
Да, но это пример другой проблемы. Здесь надо только абстрагировать интерфейс и реализацию работы с БД. При этом у вас вполне может по-прежнему быть иерархическая структура без DI.
В зависимости от размера приложения, скоро или нет вам захочется автоматизации.
В зависимости от размера приложения, скоро или нет вам захочется автоматизации.
На здоровье. Я лишь сказал о том, что приведенный выше пример из книги — о другой проблеме.
Ну, конкретно мой коммент был ответом на отрицание полезности всех абстракций в духе "Не надо гибко настраивать абстракции. Они протекать начнут."
А конкретно про DI я просто не понимаю, какую проблему он пытается решить. Есть стойкое ощущение, что такой проблемы вообще нет.
Я не против абстракций вообще. Я против преждевременной оптимизации. Я против того, что бы реализовать решение до того, как возникла проблема. А если она вообще не возникнет? А если она будет совсем другой, чем казалось разработчику (что чаще всего и бывает в жизни)?
А причем тут "преждевременная оптимизация"? Обычно этот термин применяют когда какой-то очевидно простой и не очень оптимальный алгоритм предлагают заменить на сложный и более быстрый и тут понятно, почему так стоит делать не всегда.
В случае с абстракциями, если вы не сделаете это в самом начале, потом это будет делать безумно сложнее и скорее всего, вы просто будете жить с вашим решением весь проект.
Основная идея абстракций — это сокрыть детали реализации определенного компонента, например, компонента для построения отчетов, что бы ваш код не зависел от конкретной реализации этого компонента. Если же вы решите завязаться на конкретную реализацию, то сложность ее выпиливания будет экспоненциально возрастать по мере развития проекта и довольно быстро достигнет точки, в которой компонент станет очень хрупким и вокруг него будут плодится всякие workaround'ы.
А конкретно про DI я просто не понимаю, какую проблему он пытается решить. Есть стойкое ощущение, что такой проблемы вообще нет.
Некоторой аналогией в DI можно назвать service discovery в микросервисной архитектуре. Не будете же вы отрицать, что в ней без service discovery никуда?
DI решает проблему инициализации и связывания различных компонентов вашего приложения, когда у вас их много и связывать их вручную в коде много мороки. Вместо этого вы описываете создание экземпляров этих компонентов, а они уже автоматически распихиваются в те места, где нужны.
Единственной альтернативой этому являются глобальные переменные, которые будут инициализироваться в строгой последовательности. Что на самом деле не очень круто.
Под абстракцией я имел ввиду, что если у вас компонент A зависит от компонента B, то его стоит сделать зависимым только от абстрактного компонента asbtract_B, для которого B будет имплементацией.
Для этого в Go достаточно описать интерфейс. И не надо никаких провайдеров, никаких контейнеров, никакого, не дай бог, динамического связывания. Go статически компилируемый язык и все зависимости должен проверять на этапе компиляции, а не в рантайме.
А так, используется много где, в тех же rails или django, когда вам дают динамический cursor к базе данных — это тоже DI.
Где именно в django используется «динамический cursor к базе данных» и почему это является DI?
Для этого в Go достаточно описать интерфейс. И не надо никаких провайдеров, никаких контейнеров, никакого, не дай бог, динамического связывания. Go статически компилируемый язык и все зависимости должен проверять на этапе компиляции, а не в рантайме.
- В момент инициализации приложения, а не просто в рантайме, все-таки есть небольшая разница.
- А как вы будете реализовывать в таких случаях конфигурационные файлы для выбора конкретной реализации интерфейса? Все равно читать в рантайме конфиг и подтягивать правильную реализацию.
Где именно в django используется «динамический cursor к базе данных» и почему это является DI?
Потому что вы указываете в конфиге то, к какой базе нужно подключится:
DATABASES = {
'default': {
'NAME': 'app_data',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'users': {
'NAME': 'user_data',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'priv4te'
}
}
И потом работаете с этой базой не меняя больше ничего в коде (разумеется, если вы использовали совместимые поля в моделях). Это значит, что каждая модель получает доступ к необходимому коннекту к базе данных. Как по мне, вполне похоже на DI.
Когда зависимости указываются в аннотациях (не входящих в стандарт языка), а сами модули загружаются (или не загружаются, хе-хе) на лету, путем поиска джарников по всему classpath
А как вы будете реализовывать в таких случаях конфигурационные файлы для выбора конкретной реализации интерфейса? Все равно читать в рантайме конфиг и подтягивать правильную реализацию.
Нет конечно. Я явно напишу нужную реализацию интефейса в Go коде
И что бы изменить поведение приложения, нужно будет его пересобрать?
Ну, django конфиг файл в реальной жизни я пишу так (с дефолтными значениями, но не суть):
DATABASES = {
'default': {
'NAME': os.environ.get('DATABASE_NAME'),
'ENGINE': os.environ.get('DATABASE_ENGINE'),
'USER': os.environ.get('DATABASE_USER),
'PASSWORD': os.environ.get('DATABASE_PASSWORD')
}
}
А значит, для того, что бы поменять базу мне не нужно менять код — я просто настраиваю приложение. А у вас настройка приложения только через пересборку кода — опять же, подход разумный только для мелких приложений.
А если вы забыли указать в env переменную DATABASE_NAME что с приложением будет? А если ее на лету админ поменял?
А не страшно с таким хрупким кодом жить в продакшене?
А по вашему, в код надо зашивать все до паролей от бд, что бы вдруг что, ничего нельзя было поменять?)
А если вы забыли указать в env переменную DATABASE_NAME что с приложением будет?
Дефолты, я же не копировал конфиг, писать их в комментарии мне было лень.
А если ее на лету админ поменял?
А что должно произойти? Ну да, приложение переключится на другую базу.
В go находится место даже для динамической типизации через interface{}
, я думаю, место какому-то минимальному
if cute {
a := CuteLogic{}
} else {
a := UglyLogic{}
}
Найдется.
Хм, возможно, я не совсем вас понял, но разве затирание типа через приведение к interface{} и потом приведение обратно к типу не считается попыткой получения динамической типизации?
А пример я привел просто того, как можно изменять поведение собранной программы. Так же можно и реализовать то, для чего нужно DI.
Гораздо проще и прозрачнее было бы поместить в структуру Server все нужные интерфейсы, создавать во время запуска приложения конкретные экземпляры, такой же построчно похожий симпатичный код получится. Зато любой сходу поймет!
Предположим, что функция buildMyConfigSomehow теперь запрашивает аргумент. А значит, доступ к этому аргументу теперь нужен при любом вызове этой функции.
Не обсуждая саму библиотеку, вопрос чисто по логике.
Но почему просто не создать функцию getMyConfig, которая никогда не будет запрашивать аргументов, но будет знать как вызывать buildMyConfigSomehow?
var config *Config
func GetConfig() *Config {
if config == nil {
config = NewConfig(param1)
}
return config
}
В реальных приложениях у вас будут десятки классов и структур и если не использовать DI, main превратится к огромных кусок нечитаемого и неподдерживаемого кода.
Во-первых, в Go нет классов, что не может не радовать.
Во-вторых, структур конечно может быть очень много, но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5. Если это не так, то у вас проблемы с архитектурой, и надо разбивать монолит на более мелкие компоненты.
я в корне не согласен с тем, что DI, распространен только в Java. Приведу, возможно, неожиданные примеры из фронтенда: DI — основная архитектурная идея Angular
Очень характерный пример. Angular — это идеологически таже Java, только поверх JS
Во-первых, в Go нет классов, что не может не радовать.А какие отличия между «классом» и «структурой»?
структур конечно может быть очень много, но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5.Ну это уж совсем мальенькое приложеньице. Для таких всякие DI ясен пень будут жутким оверкилом.
Очень характерный пример. Angular — это идеологически таже Java, только поверх JSПростите, но WAT?
А какие отличия между «классом» и «структурой»?
В структуре нет методов, только данные.
А также нет наследования, полиморфизма, да и инкапсуляции честно говоря практически нет.
Это просто данные, слегка структурированные.
class User {
String name;
int age;
}
В структуре нет методов, только данные.
Зато есть квазинаследование, которое называется встраивание. Так что методы в структуре тоже есть, просто объявляются в другом месте.
В структуре нет методов, только данные.Мы все еще про Go говорим?
С некоторых пор кто-то решил, что в структуру можно добавлять методы и классификация поехала в /dev/null
Во-вторых, структур конечно может быть очень много, но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5. Если это не так, то у вас проблемы с архитектурой, и надо разбивать монолит на более мелкие компоненты.
Я нахожу довольно ироничным, что для того, что бы избежать DI, вы предлагаете тот же самый DI на основе микросервисной архитектуры, только еще более оверхедный и запутанный.
Кому надо? Какие проблемы? Много сервисов? Это не проблема само по себе: если нужно 50 сервисов бизнесу, то ему всё равно будут они в одном монолите или разбиты на 50 микросервисов. Скорее даже выберет монолит при прочих равных, поскольку накладные расходы на эксплуатацию заведомо меньше
Микросервисы — это не решение проблемы больошого количества сервисов в монолите, это решение других проблем типа независимого масштабирования или необходимости останавливать все сервисы когда реально нужно только один стопнуть.
Это не проблема само по себе: если нужно 50 сервисов бизнесу, то ему всё равно будут они в одном монолите или разбиты на 50 микросервисов.
Бизнесу наверно все равно, а вот разработчику совсем нет. Я с трудом могу представить себе разработчика, который сможет удержать в голове одновременно 50 сервисов на go-рутинах, которые общаются между собой по многочисленным каналам (и не словит при этом deadlock). Логично разделить эти 50 сервисов на более крупные блоки функциональности для борьбы с когнитивной сложностью.
DI — основная архитектурная идея Angular
к нам, недавно, в компанию, пришел новый программист, который сделал попытку замутить DI на основе npm, а задача стояла — создать xml-парсер для чтения конкретного источника с данными которого должны работать сотрудники компании… вместо написания обычного класса на рнр нативными средствами для разбора хмл, было принято стягивать кучу зависимостей ради модного подхода и поставить под угрозу нормальное обновление лайва, жизнь которого зародилась, когда в рнр еще не было namespace'ов…
так и крутится в деве, и сотрудники по ссилке делают нужные импорты…
а вообще…
main превратится к огромных кусок нечитаемого и неподдерживаемого кода
main — должна задать вектор куда идти, и далее — все разветвляется по коду и пакетах, если Ваша main больше 100 строк кода — значить с архитектурой программы чтото не так…
Не надо так делать. Надо расслабится, сдаться, и принять идиоматику нового языка. Это гораздо более продуктивно. Но конечно нелегко морально. Инерция мышления — очень сильная штука.
Появляются новые идеи, решения, подходы. Выходят новые версии языка (внезапно!).
Если не смотреть иногда по сторонам (ну так, на всякий случай), можно оказаться в уютной но глубокой яме.
Как, например, наболевшая проблема с зависимостями. Можно конечно сказать «фу всяким go dep, это не идеоматично». А можно посмотреть как сделано у соседей и перенять лучшие идеи.
Как по мне, но вы высказываетесь немного аггресивно.
Программисты, которые переходят с одного языка на другой, часто приносят с собой идиоматику старого языка и пытаются запинать ее грязными ногами в новый язык.Т.е. предлагают применить давно проработанный и обкатанный архитектурный паттерн. Применяемый во многих языках. Да, реализация получилась весьма далекой от совершенства. Да, она не идеоматична… Но может лучше разобраться что и как, попытаться сделать ее идеоматичной, адаптировать… Нежели нужно сразу опускаться до язвлений в стиле «грязные ноги»?
Все это хорошо и красиво звучит, пока не сталкиваешься с проблемой, которая идиоматикой не решается нормально, потому что идиоматика просто не предусмотрела её решения.
И ладно бы это был бы какой-то rocket science, так нет же — обычная рутина, решенная уже не раз в других языках. Ты при этом как бы и не против идиоматики — спрашиваешь у опытных, и в итоге получаешь: "это не идиоматично, это тебе не нужно" (с)… ну зашибись теперь… а проблема типа "сама как-нибудь чудом там решится… может быть… ну или пропадёт… может быть".
Следующий этап — пытаешься решить сам (велосипед, ага) идиоматичными инструментами. И так, и эдак, крутишься, вертишься, но постоянно при этом теряешь другие плюшки языка — то здравствуй interface{}
, и пока type safety, то горы бойлерплейта писанного руками и потому абсолютно неподдерживаемого.
Дальше до тебя начинает доходить осознание, что во всех других языках эта проблема решается именно так, потому что есть некие фундаментальные требования/ограничения, решения которых идиоматикой Go просто тупо не предусмотрено. И инерция мышления тут вообще ни причем, сколько не бейся, и сколько не разгоняй своё мышление. И начинается финальный акт, когда ты "замазываешь" дыры в дизайне Go банальной кодогенерацией, для которой, между прочим, тоже не предусмотрено никакого стандартного тулчейна/подхода/инструмента, и каждый кодогенерирует как умеет.
В результате, ты часто не пишешь код, но пишешь код, который генерирует код, причем на каких-то костылях. Что никак не user friendly, но вменяемой альтернативы — просто нет.
Чтобы не быть голословным — вот задачка: сделайте реализацию канала в Go, который при полном заполнении буфера не блокирует последующую попытку записи, ожидая чтения (стандартное поведение буферизированного канала в Go), но выкидывает самое старое значение и принимает новое, не блокируя пишущего в канал. А после того как реализуете и отладите (особенно доставят проблемы с утечкой ресурсов при окончании работы с каналом), сделайте так, чтобы эту реализацию можно было использовать для произвольного типа, желательно включая базовые, без необходимости ваять всякие обертки (бойлерплейт, ага), при этом сохраняя type safety (проверку типов на этапе компиляции).
Аргументы "type safety не нужно, interface{}
рулит" и "вам подобные каналы не нужны"(с) не принимаются, ибо, во-первых, использовать interface{}
вроде как не идиоматично, и вполне себе дорого (тайпкасты недешевые), да и гарантии на стадии компиляции никогда не лишние, а, во-вторых, подобные каналы нужны на практике.
Go имеет много плюсов, и во многом хорош, но и недостатки, как и конкретные проблемы, у этого языка есть. Отрицать их наличие, ссылаясь на идиоматику и инерцию мышления, — это тупо закрывать глаза на проблемы. А запинывать решения грязными сапогами в Go приходится потому, что он, как оказывается достаточно часто, просто не предлагает никакого решения взамен.
Начали про безумный страшный DI, а теперь уже скатились к дженерикам :-)
Использовал DI в Go в промышленных масштабах, так сказать. Сотни объектов (хотя, возможно уже несколько тысяч). Из моей практики есть 2 аспекта:
(+)
DI хорош, когда есть множество зависимостей и всех их нужно связывать между собой. Графо-решалка всё сама подставит куда нужно.(-)
Грабли с Duck-Typing: представим, что конструктор объекта принимает интерфейс, а DI успешно в него подставляет нужный объект по этому интерфейсу. Потом проходит время, вы в совершенно независимом месте добавляете новый метод, а при запуске ловите конфликт зависимостей: DI откуда-то взял 2 объекта, подходящих под этот интерфейс. Почему? Да потому что Duck-Typing: вы случайно написали метод, который вдруг сматчил ещё один объект с этим интерфейсом. И если ваш DI-механизм не умеет показывать, откуда он это взял (а у нас самописный, который по началу не умел), то так сказать "счастливой отладки". :) Простым решением являетсяGoLand
, который умеет показывать всех представителей данного интерфейса.
Вообще для больших проектов на Go, где сотни и тысячи объектов, DI является неплохой инвестицией, ИМХО. Потому что ковыряться во всех этих "проводах" уже при сотне объектов становится адом.
Внедрение зависимостей в Go