Обновить

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

такой код мне совсем не радует глаз,
должно быть только 1 строчка кода для подключения (+отключение) куда угодно,
например так:

func main() { stopapp.StartWaitStop() mssql_gorm.StartDB() postgres_gorm.StartDB() nats.StartNats() camunda.StartCamunda() liveness.Start() stopapp.Wait_GracefulShutdown() }


как в моём стартере:
https://github.com/ManyakRus/starter

Красиво. Но где здесь внедрение зависимостей? Тут запуск каких-то абсолютно независимых друг от друга компонент.

Мой пример кода делает всё то же самое,
что и ваш огромный main,
а именно:

  • Подключение к PostgreSQL

  • Подключение REST

  • Подключение Liveness (CheckHealth)

  • Отключение с Graceful Shutdown.


    Вы напрасно создаёте слишком много кода и зависимостей.

Вы не ответили на вопрос. В вашем примере кода нет внедрения зависимостей.

Здесь есть только одна зависимость: подключение к PostgreSQL желательно делать раньше чем подключение REST, что и учитывается в моём коде.
Больше нет тут никаких зависимостей, и не должно быть.
Все зависимости только в вашей голове - чтобы усложнить самому себе жизнь.

Вы неверно трактуете термин "зависимость", по-моему. Вы слышали про Dependency injection?

А как писать тесты без зависимостей? )

Александр, мне кажется, основная цель вашего комментария - реклама собственного проекта... ((

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

Я не представляю как с этим вообще удобно работать, что в IDE что точечно в гитхабе. По ресурсам все тоже печально, можно было бы не заморачиваться с единым main а просто прописывать init() в ваших "модулях" и все. Один хрен никакого внешнего взаимодействия меж модулями нет, только прямые инициализации

Спасибо за конструктивную, аргументированную критику, за добрые слова! И вам не хворать! ))

да я про manyakRus если что

там даже в три экрана корневое дерево не влезло

А... Понял. Но вам всё равно не хворать! ))

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

объект должен быть готов к использованию сразу после завершения конструктора

А если инициализация занимает час?

    sqlSet, err := sqlset.New(queries.QueriesFS)
    if err != nil {
        return nil, fmt.Errorf("failed to load queries: %v", err)
    }

    a.repository = postgres.New(sqlsetpgxhelper.New(sqlSet))

    a.usecase = usecase.New(a.repository)

А что если в этом примере не пихать сразу поле в структуру а создать переменную, а в конце функции вернуть заполненный App, у которого вызвать метод Run скажем который запустить горутинки с серверами и брокерами?

Я в начале статьи об этом и писал. В реальности получается main на 6 экранов и больше. Читается плохо, модифицируется плохо.
Если сервис совсем простенький (как в моём примере - HTTP + БД) - да, можно не городить огород а делать всё как обычно, как вы и описали.
Но вот сейчас я работаю не над микросервисом, а над монолитным проектом на Go и тут всё пихать в main - самоубийственно.

Ну вынесите из main в другой пакет)

Если прям монолит монолит, то напишите обертку для репозиториев брокеров и сервисов)

Да без проблем. А что если всё-таки "пихать поле в структуру"? )) Что плохого в моём подходе-то? Мне он кажется более удобным. Он полностью снимает заботу о порядке создания зависимостей. И он позволяет максимально легко их добавлять.
Но, безусловно, можно всё сделать и без него, традиционным способом. Код main получится побольше, читаться и правиться будет чуть хуже. Зато просто и без ухищрений.
Тут уже дело вкуса, ИМХО.

Добрый день, не понимаю а почему не использовали fx: https://github.com/uber-go/fx
Там из коробки есть fx.Lifecycle с хуками OnStart/OnStop, контролируемый порядок запуска зависимостей, graceful shutdown по сигналам, и health checks. По сути, то что в статье выстраивалось через lazy singleton + sync.Once. Аргумент про «философию языка» очень спорный fx активно используется в продакшене в самом Uber и в других крупных компаниях ( 7.4k stars говорит само за себя)

давно перешли на него благодаря удобство и простоте, есть app.go куда подключается модули.

func NewApp(
	cfg *config.AppConfig) fx.Option {
	return fx.Module("app",
		NewAPI(cfg),
		NewStorages(cfg),
		NewProducerQueue(cfg),
		NewUseCases(cfg),
		fx.Invoke(telemetry.NewTracer),
		fx.Invoke(metrics.RunPrometheusMetrics),
		fx.Invoke(StartServer),
	)
}

И вот так выглядит main

...
application := fx.New(app.NewApp(&cfg))
	if err = application.Err(); err != nil {
		logger.Error("running application failed", zap.Error(err))
		return
	}
	application.Run()
...


P.S. не хотел обидеть, интересная статья, интересный ход мыслей по ходу статьи, но к концу как будто бы было написано половина fx)))

Спасибо за ссылку и добрые слова! Не использовал fx по двум причинам - конкретно про этот фреймворк не знал и "данный подход не соответствует философии языка: простота, читаемость, отсутствие скрытой логики." - это я в начале упомянул.
На самом деле, будь моя воля, я бы использовал fx. Но в команде есть тимлид и другие разработчики и у них мнение, увы, не всегда совпадает с моим.
Кроме того цель данной статьи помимо прочего - познавательная. И если бы я просто написал - "берите fx и не думайте", это было бы не интересно. Велосеипеды в боевой разработке изобретать не нужно, но крайне полезно для профессионального развития.
Обычно против DI фреймворков выдвигают два аргумента - магия это зло и - зачем тащить лишнюю зависимость. Но как по мне - fx калссный (uber вроде плохого не делает) и я бы использовал его. Ща пойду пробовать. Еще раз сапсибо за ссылку!

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

только при запуске будет видно

Есть же простейший тест, который покажет каких зависимостей не хватает.

func TestNewApp(t *testing.T) {
	type args struct {
		cfg    *config.AppConfig
		logger *zap.Logger
	}
	tests := []struct {
		name string
		args args
		want *fx.App
	}{
		{
			name: "default",
			args: args{
				cfg:    &config.AppConfig{},
				logger: zap.NewNop(),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := fx.ValidateApp(NewApp(tt.args.cfg, tt.args.logger)); err != nil {
				t.Errorf("NewApp() error = %s", err)
				return
			}
		})
	}
}

километром трейсов без явной причины

Если ошибка в рантайме, да сам трейс длинный, но конкретная ошибка всегда будет в последней строчке.

NewPostgresConnector.postgres.WithInstance: failed to connect to user=*** database=mydb: 127.0.0.1:54321 (127.0.0.1): dial error: dial tcp 127.0.0.1:54321: connect: connection refused"
Скрытый текст

2026-02-24T08:32:59.423+0300 error cmd/main.go:51 running application failed {"error": "could not build arguments for function "project/internal/infrastructure/api/http/login".Router (/path/to/project/internal/infrastructure/api/http/login/router.go:24): failed to build usecases.LoginUsecase: could not build arguments for function "project/internal/domain/usecases".NewLoginUsecase (/path/to/project/internal/domain/usecases/login.go:34): failed to build usecases.AuthStorage: could not build arguments for function "reflect".makeFuncStub (/path/to/sdk/go1.23.5/src/reflect/asm_arm64.s:29): failed to build connectors.PostgresConnector: received non-nil error from function "project/pkg/connectors".NewPostgresConnector (/path/to/project/pkg/connectors/postgres.go:44): NewPostgresConnector.postgres.WithInstance: failed to connect to user=*** database=mydb: 127.0.0.1:54321 (127.0.0.1): dial error: dial tcp 127.0.0.1:54321: connect: connection refused"}

Я тоже считаю, что страшилки про использования фреймворков DI - сильно преувеличены. Но согласен с тем, что наличие "магии" противоречит философии Go. С другой стороны - всё это очень условно. Разве func Marshal(v any) ([]byte, error) - это не магия? ))

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

Публикации