«Просто свяжи всё в main, логику вынеси в пакеты, держи
main.go в двадцать строк». Так советуют. Совет не то чтобы неверный, но ведёт к конкретной поломке. Вы получаете папку
cmd/ из пакетов, которые вызываются ровно из одного места, и main.go на двадцать строк вида «вызвать функцию бутстрапа в internal/app». Вы добавили лишний слой, но не добавили абстракции.➡️ Что делать вместо
Пусть
main делает настоящую работу. Читает конфиг, инициализирует пул базы, связывает зависимости, поднимает gRPC сервер и регистрирует обработчик остановки. Если ваш
main.go на двести строк, но каждая строка это честная проводка без бизнес логики и без условий по фичефлагам, это нормально:func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
cfg := config.MustLoad() // паникует на плохом конфиге, намеренно на старте
pool, err := pgxpool.New(ctx, cfg.DatabaseURL)
if err != nil {
log.Fatal("failed to initialize database pool", zap.Error(err))
}
defer pool.Close()
queries := db.New(pool)
memberSvc := members.NewService(queries)
srv := grpc.NewServer(grpc.ChainUnaryInterceptor(
logging.UnaryServerInterceptor(logger),
recovery.UnaryServerInterceptor(),
))
memberspb.RegisterMembersServiceServer(srv, memberSvc)
go func() {
if err := srv.Serve(lis); err != nil {
log.Fatal("server exited", zap.Error(err))
}
}()
<-ctx.Done()
srv.GracefulStop()
}Это читаемо, и это реальная топология приложения, видная в одном файле. Тонкий
main оправдан, только когда стартовая логика действительно сложная и заслуживает отдельного пакета. В остальных случаях честная проводка прямо в main понятнее.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction