Pull to refresh

Comments 92

UFO just landed and posted this here
по-моему, это ужасно… Ребята с Убера молодцы, конечно…

по-моему #2: у меня складывается впечатление, что все это ради красивого тела main()…

опыт шепчет: чем больше явного, тем лучше…

а для проекта, средней сложности и объема, использование такого подхода приведет к тому, что для фикса мелочи незнакомым человеком, придется просмотреть и вникнуть во все щели кода и логики, абы ничего не упустить и не сломать.
Имею опыт работы как с Go, так и с PHP (Laravel). Насколько красиво и удобно смотрится DI в Laravel, например, настолько неудобно и чужеродно подобный DI смотрится в Go. Понятно, что разная типизация и вот это все… Но хорошего DI для Go я так пока и не увидел.
И согласен, лучше, если код более явный, если хорошей «магии» из-за особенностей языка не применить.
Он достаточно умён, чтобы создавать только один экземпляр каждого предоставленного типа

Создаёт только синглтоны?

Если я правильно понимаю со слов автора — 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 плохо подходит для написания больших приложений, то внедрение зависимостей предлагается делать через сервисную архитектуру.

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

Все таки Java головного мозга очень тяжело поддается лечению.

Это просто пример такой. А когда таких "простых и явных" мест будет десятки и изменение в одном месте надо будет дублировать в остальных — вот тут и придет нужна в DI. А еще DI нужен для абстрактных развязок, что бы была возможность гибко настраивать абстракции.

Не надо гибко настраивать абстракции. Они протекать начнут.

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

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

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


Почему-то программисты, которые любят рассказывать про "дырявые" абстракции не предлагают альтернативу, так как ее обычно нет.


Под абстракцией я имел ввиду, что если у вас компонент A зависит от компонента B, то его стоит сделать зависимым только от абстрактного компонента asbtract_B, для которого B будет имплементацией. И это стоит делать как минимум для того, что бы разделить эти компоненты и дать возможность менять их независимо. Могу отправить вас почитать "Чистую архитектуру", поверьте, стоит потратить на это немного времени.


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

И именно поэтому в комплекте с Go вам предлагают пользоватся микросервисами, что бы прокладывать храницы на уровне API спецификаций. Я нахожу это несколько ироничным, потому что для написания крупных приложений приходится управлять кучей микросервисами с оверхедами на сетевые запросы вместо вызовов функций, и плюсы микросервисной архитектуры не всегда это окупают, даже если получится их сохранить.


Нигде, кроме Джавы, DI широко не используются. И ничего, живут как то люди, и неплохо живут.

Даже в C#? Ой, вряд ли. А так, используется много где, в тех же rails или django, когда вам дают динамический cursor к базе данных — это тоже DI.

Не обижайтесь, но вам абсолютно верно указали.
Описанный подход лично я считаю вредительским и явным оверинжинирингом.
Вместо того чтобы сделать нормальные абстракции поверх интерфейсов, а ещё лучше вообще их не делать, пока не возникло ВНЕЗАПНО НЕОБХОДИМОСТИ поменять реализацию коннекта к базе или ещё чего-то такого (как по мне, это все равно невозможно сделать таким способом irl)
Посыл то правильный — надо писать нормальный тестируемый код с нормальными абстракциями и разделением ответственности, но реализация просто чудовищная
Вместо того чтобы сделать нормальные абстракции поверх интерфейсов, а ещё лучше вообще их не делать, пока не возникло ВНЕЗАПНО НЕОБХОДИМОСТИ поменять реализацию коннекта к базе или ещё чего-то такого


Золотые слова! Не надо заранее изобретать абстракцию, которая с вероятностью 95% никогда не понадобится!

Как бы, именно в этом проблема. В том, что эти 5%, которые вы надеетесь сделать потом, вы не сделаете.


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

У нас есть класс который пронизывает весь код, мы делаем из класса интерфейс, и все, компилятор сам покажет где надо поменять одну релизацию на другую.

Если вы опираетесь на какую-то технологию, то скорее всего:


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

Поэтому вы прикидываете, что надо будет переписать и перепроверить весь код приложения и грустно вздыхая откладываете это. В той же "чистой архитектуре" есть отличный пример, когда они сильно завязались на 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 проект, но и там все получается относительно неплохо.

Или эмулировать масссивы для MySQL через сериализованные поля. Собственно задача перейти на Postgre может возникнуть, когда єта ємуляция станет очень дорогой, дороже плюсов мускуля.
В той же «чистой архитектуре» есть отличный пример, когда они сильно завязались на SQL диалект одной базы, а потом, когда попытались перейти на другую из-за того, что поддержка первой закончилась — столкнулись с тем, что они не могут это сделать не переписав все приложени

Да, но это пример другой проблемы. Здесь надо только абстрагировать интерфейс и реализацию работы с БД. При этом у вас вполне может по-прежнему быть иерархическая структура без DI.
То есть в main() вы будете сначала настраивать низкоуровневые компоненты, а потом вручную связывать их с высокоуровневыми?

В зависимости от размера приложения, скоро или нет вам захочется автоматизации.
Почему в main? По месту использования.

В зависимости от размера приложения, скоро или нет вам захочется автоматизации.

На здоровье. Я лишь сказал о том, что приведенный выше пример из книги — о другой проблеме.

Ну, конкретно мой коммент был ответом на отрицание полезности всех абстракций в духе "Не надо гибко настраивать абстракции. Они протекать начнут."

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

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

А причем тут "преждевременная оптимизация"? Обычно этот термин применяют когда какой-то очевидно простой и не очень оптимальный алгоритм предлагают заменить на сложный и более быстрый и тут понятно, почему так стоит делать не всегда.


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


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


А конкретно про DI я просто не понимаю, какую проблему он пытается решить. Есть стойкое ощущение, что такой проблемы вообще нет.

Некоторой аналогией в DI можно назвать service discovery в микросервисной архитектуре. Не будете же вы отрицать, что в ней без service discovery никуда?


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


Единственной альтернативой этому являются глобальные переменные, которые будут инициализироваться в строгой последовательности. Что на самом деле не очень круто.

Абстракция, скорытие — это тоже оптимизация. Минимизация количества связей с конкретной реализацией.
DI решает проблему слишком большого количества ответственностей. Грубо, он снимает с вашего класса ответственность за создание своих зависимостей, перекладывая её на внешний мир.
Под абстракцией я имел ввиду, что если у вас компонент A зависит от компонента B, то его стоит сделать зависимым только от абстрактного компонента asbtract_B, для которого B будет имплементацией.


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

А так, используется много где, в тех же rails или django, когда вам дают динамический cursor к базе данных — это тоже DI.


Где именно в django используется «динамический cursor к базе данных» и почему это является DI?
Для этого в Go достаточно описать интерфейс. И не надо никаких провайдеров, никаких контейнеров, никакого, не дай бог, динамического связывания. Go статически компилируемый язык и все зависимости должен проверять на этапе компиляции, а не в рантайме.

  1. В момент инициализации приложения, а не просто в рантайме, все-таки есть небольшая разница.
  2. А как вы будете реализовывать в таких случаях конфигурационные файлы для выбора конкретной реализации интерфейса? Все равно читать в рантайме конфиг и подтягивать правильную реализацию.

Где именно в 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.

А по мне, так обыкновенный конфиг. Причем тут DI?
Ну апофеоз DI это Spring.
Когда зависимости указываются в аннотациях (не входящих в стандарт языка), а сами модули загружаются (или не загружаются, хе-хе) на лету, путем поиска джарников по всему classpath
Мне кажется вы переносите свой личный опыт касательно конкретной реализации (вероятно не самый приятный опыт) на весьма общий архитектурный паттерн.

Spring != DI.
Параметры подключения — это зависимость класса подключения. Либо он их хранит в себе, либо они в него инжектятся снаружи. В случае конфига — снаружи в общем случае.
А как вы будете реализовывать в таких случаях конфигурационные файлы для выбора конкретной реализации интерфейса? Все равно читать в рантайме конфиг и подтягивать правильную реализацию.


Нет конечно. Я явно напишу нужную реализацию интефейса в Go коде

И что бы изменить поведение приложения, нужно будет его пересобрать?

да. Ровно так же, как в приведенном примере с django — там это был не внешний конфиг файл, а исполняемый python файл.

Ну, 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 гордится тем, что в результате сборки получается статически собранный бинарник. Где тут место для IOC/DI не на этапе компиляции? А если на этапе компиляции, тогда и шаблоны C++ — это такой DI. И из осмысленной концепции получается пустословие какое-то.

В go находится место даже для динамической типизации через interface{}, я думаю, место какому-то минимальному


if cute {
a := CuteLogic{}
} else {
a := UglyLogic{}
}

Найдется.

Вы уверены, что не путаете динамическую типизацию с динамической компоновкой?

Хм, возможно, я не совсем вас понял, но разве затирание типа через приведение к interface{} и потом приведение обратно к типу не считается попыткой получения динамической типизации?


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

Мой комментарий был о том, что DI — это механизм времени компоновки, а динамическая типизация — скорее времени компиляции. То, о чем статья — скорее относится к IOC, чем к DI. Например, инстанциация шаблона конкретным типом — это может быть IOC. И инициализация переменной типа интерфейс конкретным типом тоже. А вот подстановка нужного типа на этапе загрузки и выполнения программы — уже DI. Естественно, имхо.
В PHP широко используется.
Ну да, давайте еще у PHP передовые концепции перенимать. Как будто нам Java мало.
Ну конечно нет, зачем смотреть на чужой опыт. Лучше все делать самим, с нуля, не так как у всех ;) Учиться исключительно на своих ошибках и т.п.
В шарпе и джаваскрипте ещё используются.

Гораздо проще и прозрачнее было бы поместить в структуру Server все нужные интерфейсы, создавать во время запуска приложения конкретные экземпляры, такой же построчно похожий симпатичный код получится. Зато любой сходу поймет!

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

Не обсуждая саму библиотеку, вопрос чисто по логике.
Но почему просто не создать функцию getMyConfig, которая никогда не будет запрашивать аргументов, но будет знать как вызывать buildMyConfigSomehow?
var config *Config

func GetConfig() *Config {
	if config == nil {
		config = NewConfig(param1)
	}
	return config
}

Не знаток Go, но разве в нём глобальные переменные не считаются злом по умолчанию?
Автор выше реализовал синглтон. Переменная config в целом не глобальна, т.к. начинается не с Заглавной буквы, и видима только внутри какого-то своего пакета.
Переменная config в целом не глобальна

Вполне себе глобальная приватная (скрытая) переменная.
У меня сложилось впечатление, что все пытаются критиковать данный подход из-за, якобы, переусложнения и неявности. Но почему-то все забывают что, во-первых, это игрушечный пример, в котором затраты на создание и обслуживание контейнера только начинают быть сопоставимы и пользой от него. В реальных приложениях у вас будут десятки классов и структур и если не использовать DI, main превратится к огромных кусок нечитаемого и неподдерживаемого кода. Данный пример призван просто продемонстрировать концепцию, если бы он был сложнее, тем, кто впервые сталкнулся с понятием DI, было бы неимоверно сложно понять в чем же его суть. Во-вторых, я в корне не согласен с тем, что DI, распространен только в Java. Приведу, возможно, неожиданные примеры из фронтенда: DI — основная архитектурная идея Angular, с другой стороны React Context — своего рода DI для распространения данных и функционала между компонентами (а возможно и других компонентов). В-третьих, неужели ни у кого из комментаторов не возникало потребности иметь в системе две имплементации схожего функционала? И дело может быть не в таких фундаментальных сущностях как база данных, а, например, в алгоритме расчета налога в зависимости от страны или других персонализированных алгоритмах. DI — это зрелая и сложившаяся концепция которая показывает свою практическую полезность на протяжении уже долго времени. Довольно странно слышать, что это оверинженириг или глупое усложнение. Это техника, которая может показаться избыточной на «короткой дистанции», но если у вас действительно сложный коммерческий продукт, без DI вы просто в определенный момент начнете строить диаграммы зависимостей на бумажке, в то время как DI дает возможность абстрагироваться от полной картины зависимостей и переложить рутинную работу на подходящий для этого инструмент.
В реальных приложениях у вас будут десятки классов и структур и если не использовать DI, main превратится к огромных кусок нечитаемого и неподдерживаемого кода.


Во-первых, в Go нет классов, что не может не радовать.

Во-вторых, структур конечно может быть очень много, но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5. Если это не так, то у вас проблемы с архитектурой, и надо разбивать монолит на более мелкие компоненты.

я в корне не согласен с тем, что DI, распространен только в Java. Приведу, возможно, неожиданные примеры из фронтенда: DI — основная архитектурная идея Angular


Очень характерный пример. Angular — это идеологически таже Java, только поверх JS
Во-первых, в Go нет классов, что не может не радовать.
А какие отличия между «классом» и «структурой»?

структур конечно может быть очень много, но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5.
Ну это уж совсем мальенькое приложеньице. Для таких всякие DI ясен пень будут жутким оверкилом.

Очень характерный пример. Angular — это идеологически таже Java, только поверх JS
Простите, но WAT?
А какие отличия между «классом» и «структурой»?


В структуре нет методов, только данные.
А также нет наследования, полиморфизма, да и инкапсуляции честно говоря практически нет.
Это просто данные, слегка структурированные.
Класс может быть без методов. А в Go мы можем добавить методы структуре.
В структуре нет методов, только данные.

Зато есть квазинаследование, которое называется встраивание. Так что методы в структуре тоже есть, просто объявляются в другом месте.

Встраивание, это ни разу не наследование. Это композиция.

Это квазинаследование, а не композиция, потому что допустим такой код.


Была бы чистая композиция, так бы нельзя было делать)

Чем будет отличаться от метода поле структуры, которому присвоено значение функционального типа?
В структуре нет методов, только данные.
Мы все еще про Go говорим?
Это уже чисто терминологический спор.
С некоторых пор кто-то решил, что в структуру можно добавлять методы и классификация поехала в /dev/null
Во-вторых, структур конечно может быть очень много, но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5. Если это не так, то у вас проблемы с архитектурой, и надо разбивать монолит на более мелкие компоненты.

Я нахожу довольно ироничным, что для того, что бы избежать DI, вы предлагаете тот же самый DI на основе микросервисной архитектуры, только еще более оверхедный и запутанный.

> но больших сервисов, которые запускаются из main, вряд ли будет больше 3-5. Если это не так, то у вас проблемы с архитектурой, и надо разбивать монолит на более мелкие компоненты.

Кому надо? Какие проблемы? Много сервисов? Это не проблема само по себе: если нужно 50 сервисов бизнесу, то ему всё равно будут они в одном монолите или разбиты на 50 микросервисов. Скорее даже выберет монолит при прочих равных, поскольку накладные расходы на эксплуатацию заведомо меньше

Микросервисы — это не решение проблемы больошого количества сервисов в монолите, это решение других проблем типа независимого масштабирования или необходимости останавливать все сервисы когда реально нужно только один стопнуть.
Это не проблема само по себе: если нужно 50 сервисов бизнесу, то ему всё равно будут они в одном монолите или разбиты на 50 микросервисов.


Бизнесу наверно все равно, а вот разработчику совсем нет. Я с трудом могу представить себе разработчика, который сможет удержать в голове одновременно 50 сервисов на go-рутинах, которые общаются между собой по многочисленным каналам (и не словит при этом deadlock). Логично разделить эти 50 сервисов на более крупные блоки функциональности для борьбы с когнитивной сложностью.
Кто сказал, что общаются? Они могут быть разделены, не иметь общих данных, по крайней мере на запись, разве что какой-то роутер решает какой из 49 дернуть.
DI — основная архитектурная идея Angular


к нам, недавно, в компанию, пришел новый программист, который сделал попытку замутить DI на основе npm, а задача стояла — создать xml-парсер для чтения конкретного источника с данными которого должны работать сотрудники компании… вместо написания обычного класса на рнр нативными средствами для разбора хмл, было принято стягивать кучу зависимостей ради модного подхода и поставить под угрозу нормальное обновление лайва, жизнь которого зародилась, когда в рнр еще не было namespace'ов…

так и крутится в деве, и сотрудники по ссилке делают нужные импорты…

а вообще…

main превратится к огромных кусок нечитаемого и неподдерживаемого кода


main — должна задать вектор куда идти, и далее — все разветвляется по коду и пакетах, если Ваша main больше 100 строк кода — значить с архитектурой программы чтото не так…
Каждый язык программирования имеет свою идиоматику. Программисты, которые переходят с одного языка на другой, часто приносят с собой идиоматику старого языка и пытаются запинать ее грязными ногами в новый язык. Ничего хорошего из этого обычно не получается. Рождаются нежизнеспособные монстры, которых очень тяжело читать и поддерживать. (А не забываем. что поддержка — это 90% жизненного цикла любого успешного программного продукта)

Не надо так делать. Надо расслабится, сдаться, и принять идиоматику нового языка. Это гораздо более продуктивно. Но конечно нелегко морально. Инерция мышления — очень сильная штука.

Язык это не что-то неподвижное и перманентное.
Появляются новые идеи, решения, подходы. Выходят новые версии языка (внезапно!).
Если не смотреть иногда по сторонам (ну так, на всякий случай), можно оказаться в уютной но глубокой яме.

Как, например, наболевшая проблема с зависимостями. Можно конечно сказать «фу всяким go dep, это не идеоматично». А можно посмотреть как сделано у соседей и перенять лучшие идеи.
Смотреть по сторонам очень полезно, но применять это лучше отталкиваясь от базовой идиоматики, не поперек идеологии языка.
А что, кто-то разве говорит иначе и спорит?

Как по мне, но вы высказываетесь немного аггресивно.
Программисты, которые переходят с одного языка на другой, часто приносят с собой идиоматику старого языка и пытаются запинать ее грязными ногами в новый язык.
Т.е. предлагают применить давно проработанный и обкатанный архитектурный паттерн. Применяемый во многих языках. Да, реализация получилась весьма далекой от совершенства. Да, она не идеоматична… Но может лучше разобраться что и как, попытаться сделать ее идеоматичной, адаптировать… Нежели нужно сразу опускаться до язвлений в стиле «грязные ноги»?

Все это хорошо и красиво звучит, пока не сталкиваешься с проблемой, которая идиоматикой не решается нормально, потому что идиоматика просто не предусмотрела её решения.


И ладно бы это был бы какой-то rocket science, так нет же — обычная рутина, решенная уже не раз в других языках. Ты при этом как бы и не против идиоматики — спрашиваешь у опытных, и в итоге получаешь: "это не идиоматично, это тебе не нужно" (с)… ну зашибись теперь… а проблема типа "сама как-нибудь чудом там решится… может быть… ну или пропадёт… может быть".
Следующий этап — пытаешься решить сам (велосипед, ага) идиоматичными инструментами. И так, и эдак, крутишься, вертишься, но постоянно при этом теряешь другие плюшки языка — то здравствуй interface{}, и пока type safety, то горы бойлерплейта писанного руками и потому абсолютно неподдерживаемого.
Дальше до тебя начинает доходить осознание, что во всех других языках эта проблема решается именно так, потому что есть некие фундаментальные требования/ограничения, решения которых идиоматикой Go просто тупо не предусмотрено. И инерция мышления тут вообще ни причем, сколько не бейся, и сколько не разгоняй своё мышление. И начинается финальный акт, когда ты "замазываешь" дыры в дизайне Go банальной кодогенерацией, для которой, между прочим, тоже не предусмотрено никакого стандартного тулчейна/подхода/инструмента, и каждый кодогенерирует как умеет.
В результате, ты часто не пишешь код, но пишешь код, который генерирует код, причем на каких-то костылях. Что никак не user friendly, но вменяемой альтернативы — просто нет.


Чтобы не быть голословным — вот задачка: сделайте реализацию канала в Go, который при полном заполнении буфера не блокирует последующую попытку записи, ожидая чтения (стандартное поведение буферизированного канала в Go), но выкидывает самое старое значение и принимает новое, не блокируя пишущего в канал. А после того как реализуете и отладите (особенно доставят проблемы с утечкой ресурсов при окончании работы с каналом), сделайте так, чтобы эту реализацию можно было использовать для произвольного типа, желательно включая базовые, без необходимости ваять всякие обертки (бойлерплейт, ага), при этом сохраняя type safety (проверку типов на этапе компиляции).
Аргументы "type safety не нужно, interface{} рулит" и "вам подобные каналы не нужны"(с) не принимаются, ибо, во-первых, использовать interface{} вроде как не идиоматично, и вполне себе дорого (тайпкасты недешевые), да и гарантии на стадии компиляции никогда не лишние, а, во-вторых, подобные каналы нужны на практике.


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

Начали про безумный страшный DI, а теперь уже скатились к дженерикам :-)

Закон ГО — по мере разрастания дискуссии про Golang, вероятность обсуждения дженериков, стремится к единице.
А тема-то, вообщем, актуальная. Оригинал статьи дополнили следующим: «Google recently released their own DI container called wire» — github.com/google/wire. Но пока только alfa и для production не рекомендуют.

А ещё он на основе code generation. Т.е. сначал сгенерить код нужно. Что впрочем тоже неплохо: все косяки будут отловлены ещё в compile time.

Использовал DI в Go в промышленных масштабах, так сказать. Сотни объектов (хотя, возможно уже несколько тысяч). Из моей практики есть 2 аспекта:


  • (+) DI хорош, когда есть множество зависимостей и всех их нужно связывать между собой. Графо-решалка всё сама подставит куда нужно.
  • (-) Грабли с Duck-Typing: представим, что конструктор объекта принимает интерфейс, а DI успешно в него подставляет нужный объект по этому интерфейсу. Потом проходит время, вы в совершенно независимом месте добавляете новый метод, а при запуске ловите конфликт зависимостей: DI откуда-то взял 2 объекта, подходящих под этот интерфейс. Почему? Да потому что Duck-Typing: вы случайно написали метод, который вдруг сматчил ещё один объект с этим интерфейсом. И если ваш DI-механизм не умеет показывать, откуда он это взял (а у нас самописный, который по началу не умел), то так сказать "счастливой отладки". :) Простым решением является GoLand, который умеет показывать всех представителей данного интерфейса.

Вообще для больших проектов на Go, где сотни и тысячи объектов, DI является неплохой инвестицией, ИМХО. Потому что ковыряться во всех этих "проводах" уже при сотне объектов становится адом.

Sign up to leave a comment.