Как стать автором
Обновить

Бардак в main, стандартизация и uber.fx. Как сделать структуру кода понятнее для всех

Время на прочтение20 мин
Количество просмотров16K
Всего голосов 16: ↑15 и ↓1+21
Комментарии19

Комментарии 19

Вопрос. Рассматривали ли вариант со статической генерацией графа зависимостей, например wire и если да, то почему выбор пал на uber.fx

wire требует кодогенерации, что сразу оттолкнуло

а еще - он очень замороченный, там на одну регистрацию надо несколько конструкций городить. fx существенно проще

Двояко, например в wire нельзя делать провайдеры которые возвращают одинаковую структуру (например когда у нас есть отдельно redis для кеша, отдельно для сессий и тп) - нужно писать свои врапперы, зато при падении (или получении nil как описано ниже в комментах) будет сразу понятно что ошибка возникла в wire_gen.go:37:5 RedisSessionStore - условно, и граф зависимостей виден в файле в виде линейной последовательности вызовов - ну и тестов особо не нужно, IDE подстветит код если сгенерировалось что-то не валидное, да и golang не даст скомпилировать неправильный код, хотя риск ошибок в рантайм конечно остается

wire особенно удобен в монорепах, можно сделать DefaultSet (с логером, трейсами, grpc-clients/server) и сильно сократить объем дублирования кода

p.s. хотя еще один момент что иногда стреляет в wire это то что он игнорит зависимости если их никто не требует явно, и бывает странно, вроде добавил в зависимости Tracer а в wire_gen.go он не попал, но это скорее повод подумать об рефакторинге кода и убраь не явные ожидания от окружения

p.s. еще DI неплохо использовать не только на уровне main.go но и на уровне модулей, что бы точно также объединить одной строчкой нужные репозитории, сервисы, домены - хотя это и спорное решение

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

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

Если приложение небольшое, 10-12 компонентов, то можно обойтись без DI, потратив чуть больше времени на начальное написание компонента.

Почему вы не упомянули про то, что отладка превращается в пущий кошмар? А если происходит падение программы, то по логам невозможно определить где и почему упало, в каком модуле. Например, просто в provide прописали вызов конструктора,а сам конструктор возвращает nil. А если ещё там сложная иерархия, то вообще ужас.
Опять же, чтобы просто написать тесты, приходится наворотить тонны кода, чтобы просто обеспечить работу всех DI.
Проект конечно структурируется,но он становится очень сложным в поддержке и сопровождении. Новым людям сложно разбираться в этой магии.
А про то чтобы разобраться с иерархией вызовов я вообще молчу, потому что не очевидно,не наглядно и даже ide не помогает.
И самое главное : зачем тащить в Go идеологию Spring Boot? Здесь это не нужно.

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

Писать тесты с fx совсем не нужно - достаточно проверить в одном тесте что аппка стартует (через fxtest), а тесты всех остальных пакетов вайрить вручную.

Про дебаг, если частно, не понял - я ни разу не имел проблемы с дебагом графа зависимостей. Если у вас вместо типа где-то пришел nil, то надо просто посмотреть, почему конструктор типа его вернул, так?

Сурс: 5 лет писал код в убире с fx. В новой конторе тоже пишу на го, но без fx и вижу, какую помойку люди уже наворотили, будем всех перетаскивать на fx.

Забыли упомянуть, что пакетам приходится принимать на вход конкретные типы или описывать интерфейсы глобально. Не получится внутри пакета А декларировать интерфейс, который он получает на вход, так, чтобы пакет В не знал об этом интерфейсе. Это идет вразрез с идеологией Go.

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

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

А что касается этой присказки про “accept interface, return type”, то я, признаться, не видел, как она хорошо работает с большими проектам. Зато видел как она работает плохо, когда очень трудно найти концы, кто какой кусок функционала твоего пакета использует

Возможно, стоит использовать Go для написания микросервисов, а не монолитов. ? Для монолитов жоэс всяких куча, там вам и DI сколько угодно.

Микросервис означает лишь ограниченность скоупа, а не то, что у сервиса нету логгинга, метрик, грпц клиентов и всех прочих атрибутов взрослой инфраструктуры, которую надо как-то подключать и настраивать

Перечисленые вещи легко инкапсулируются в пакетах и не лезут в main. И это не большое количество.

С другой стороны, если у тебя там штук 20 клиентов, коннекты к нескольким БД, то возможно, твой сервис стоит разделить на несколько микросервисов.

Конечно, всегда существуют исключения, но в большинстве своём у микросервиса небольшой main.

Что значит «инкапсулируются»? Вайринг никуда не денешь, если у тебя в конструктор типа надо передать пяток аргументов, никуда от этого не деться. Да, можно всю эту вермишель запихнуть в отдельный пакет, но она никуда не денется и все равно придется в нее лазить чтобы подключать новое.

Возможно, для типичного микросервиса, который перекладывает жсоны по хттп, эта проблема не так выражена, но мир этим не ограничивается, бывают и более развесистые сервисы даже в микросервисной архитектуре - какие-нибудь кубернетесовские кастомные контроллеры/операторы, имплементации, разные оркестраторы на cadence/temporal/etc. В чем хорош fx так это в том, что он дает способ очень унифицировано выстраивать структуру приложений, так что следующему программисту вообще не надо разбираться, что где лежит и как работает, потому что он уже знает, куда ему смотреть.

В общем, я даже рискну обобщить, что причины существования спринга в жяве валидны и для голанга. Простой синтаксис, в конце концов, не означает что вы должны писать простые программы, не так ли?:)

Про "следующего программиста" и "вообще не надо разбираться" не соглашусь категорически! Сами лично столкнулись с проблемой, что в самом fx'е довольно сложно разобраться тем, кто с ним раньше не работал (5 лет в убире, например). Практика показала, что при любом неаккуратном добавлении в код новой фичи у "следующего программиста" обычно всё падает, а отлаживать это хозяйство вообще невозможно, не имея за плечами соответствующего опыта. Короче говоря, завезли когда-то, а теперь страстно желаем съехать с этой эпидерсии. Ну правда, не нужна она в Go.

Если честно, я не понимаю, где там можно заплутать. Концептуально все очень просто - есть несколько функций, которые на вход принимают какие-то типы и в ответ возвращают другие типы. Все что делает fx - сортирует это список функций и вызывает их. Все. Реально ничего больше не происходит. Разве это сложно?

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

в основном отсутствием необходимости запускать сервис )
также - проверяется только вайринг, тела функций не запускаются

При чтении постоянно возникает ощущение что просто переизобрели Erlang/OTP.

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

Разруха не в клозетах, а в головах.

Один пхпшер написал этот кусок говна для го, теперь за ним каждый 3й пытается повторить. Опыт помойки fx на проде: дебажить этот непотребство может только 1 человек - тот который его внедрял. Ну дофига у вас завимостей - ну соберите себе фабрику, или упростите сервис, вынесите в пакет\функцию старт этих зависимостей. Нет, нам надо либо огромный самопутевый контейнер с неявным пробросом, либо все говно в контекст сложить. В эфективе русским по белому сказано чем проще и прямее - тем лучше. Нет, прикорячим пародию на спринг, мыж самые умные. Споконейшим образом юниты пишутся, что сьюты, что модули, что интеграшки и без этой чепухи. Зависимости в интерфейс оборачиваешь и условный мокген решает все надуманные проблемы с тестами. А самое прекрасное - ты и без дебага точно знаешь, где, что и как инициализируется, и что куда передается. БЕЗ попытки поиграть в игру "найди почему не стартуют логи" и " найди почему не ресолвится новая ручка в роутере". Хотите нормальный лаконичный мейн? заведите платформенную либу которая делай app.Init() и потом app.Run(). Делает все одинакого и явно для 10-20 100 сервисов. Убер как раз славен тем что нанимает кучу индусов с 4мя классами, и пытается их заставить погромировать - оттого и рождает свои дюже странные решения.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий