Задать вопрос
@setupx
Go & TS developer

Правильное разделение микросервисов?

Привет! Есть свой pet-проект онлайн-магазина, REST API я захотел я его разделить на микросервисы, написал я proto файлы, сгенерировал и вроде все работает нормально, на клиенте у меня HTTP, а общение с помощью gRPC.

Но у меня остался вопрос, разве эти микросервисы должны находится в 1 репозитории и запускаться вот так:
code

// startGRPC todo: add docs
func (a *App) startGRPC(ctx context.Context) error {
	const op = "app.startGRPC"

	logger := zerolog.Ctx(ctx).With().
		Str("op", op).
		Int("port", a.cfg.ServerGRPC.Port).
		Logger()

	// Initializing all repositories
	imagesRepo := repository.NewImagesRepositoryV1(a.pgpool)

	// Initialization all services
	imagesFileService := file.NewServiceV1(imagesRepo)
	imagesWebService := web.NewServiceV1(imagesRepo)

	// Initialization all handlers
	imagesHandler := grpc2.NewImageHandlersGRPCV1(imagesFileService, imagesWebService)

	serverOptions := []grpc.ServerOption{
		grpc.MaxRecvMsgSize(a.cfg.ServerGRPC.MaxRecvMsgSize),
		grpc.MaxSendMsgSize(a.cfg.ServerGRPC.MaxSendMsgSize),
		grpc.MaxConcurrentStreams(uint32(a.cfg.ServerGRPC.MaxConcurrentStreams)),
		grpc.KeepaliveParams(keepalive.ServerParameters{
			Time:    a.cfg.ServerGRPC.KeepAliveTime,
			Timeout: a.cfg.ServerGRPC.KeepAliveTimeout,
		}),
		grpc.ConnectionTimeout(a.cfg.ServerGRPC.MaxConnectionAge + a.cfg.ServerGRPC.MaxConnectionAgeGrace),
		grpc.StatsHandler(otelgrpc.NewServerHandler()),
	}

	a.gRPCServer = grpc.NewServer(serverOptions...)

	pb.RegisterImagesServiceV1Server(a.gRPCServer, imagesHandler)

	if a.cfg.ServerGRPC.EnableReflection {
		reflection.Register(a.gRPCServer)
	}

	// todo:
	//if a.cfg.ServerGRPC.EnableHealthCheck {
	//	healthServer := health.NewServer()
	//	healthpb.RegisterHealthServer(a.gRPCServer, healthServer)
	//	healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
	//}

	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", a.cfg.ServerGRPC.Port))
	if err != nil {
		logger.Fatal().Err(err).Msg("failed to create listener gRPC")
		return fmt.Errorf("%s: %w", op, err)
	}

	go func() {
		logger.Info().Msg("starting gRPC server")
		if err = a.gRPCServer.Serve(listener); err != nil {
			logger.Fatal().Err(err).Msg("failed to server gRPC")
		}
	}()

	return nil
}

// startHTTP todo: add docs
func (a *App) startHTTP(ctx context.Context) error {
	const op = "app.startHTTP"

	logger := zerolog.Ctx(ctx).With().
		Str("op", op).
		Int("port", a.cfg.ServerHTTP.Port).
		Dur("read-timeout", a.cfg.ServerHTTP.ReadTimeout).
		Dur("write-timeout", a.cfg.ServerHTTP.WriteTimeout).
		Dur("idle-timeout", a.cfg.ServerHTTP.IdleTimeout).
		Logger()

	conn, err := a.createGRPCConn(fmt.Sprintf("localhost:%d", a.cfg.ServerGRPC.Port))
	if err != nil {
		logger.Fatal().Err(err).Msg("failed to create gRPC connection")
		return fmt.Errorf("%s: %w", op, err)
	}

	handler := imagesHTTP.NewImagesHandlersHTTP(pb.NewImagesServiceV1Client(conn))

	router := echo.New()
	router.HTTPErrorHandler = func(err error, c echo.Context) {
		var he *echo.HTTPError
		if errors.As(err, &he) && he.Code == http.StatusNotFound {
			_ = respond.NotFound(c, nil, nil)
			return
		}
		router.DefaultHTTPErrorHandler(err, c)
	}

	if a.cfg.ServerHTTP.EnableCORS {
		router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
			AllowOrigins: a.cfg.ServerHTTP.CORSAllowedOrigins,
			AllowMethods: a.cfg.ServerHTTP.CORSAllowedMethods,
			AllowHeaders: a.cfg.ServerHTTP.CORSAllowedHeaders,
		}))
	}

	if a.cfg.ServerHTTP.EnableCompression {
		router.Use(middleware.Gzip())
	}

	if a.cfg.ServerHTTP.EnableRequestLogging {
		router.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
			LogURI:    true,
			LogStatus: true,
			LogMethod: true,
			LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
				logger.Info().
					Str("method", v.Method).
					Str("uri", v.URI).
					Int("status", v.Status).
					Msg("request")
				return nil
			},
		}))
	}

	if a.cfg.ServerHTTP.EnableResponseLogging {
		router.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
			Format: `{"time":"${time_rfc3339_nano}","method":"${method}","uri":"${uri}","status":${status},"latency":${latency},"latency_human":"${latency_human}"}` + "\n",
			Output: logger,
		}))
	}

	api := router.Group("/api")
	v1 := api.Group("/v1")
	imagesRouter := v1.Group("/images")

	handler.LoadHandlers(imagesRouter)

	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", a.cfg.ServerHTTP.Port))
	if err != nil {
		logger.Fatal().Err(err).Msg("failed to create listener")
		return fmt.Errorf("failed to create listener: %w", err)
	}

	a.httpServer = &http.Server{
		Handler:        router,
		ReadTimeout:    a.cfg.ServerHTTP.ReadTimeout,
		WriteTimeout:   a.cfg.ServerHTTP.WriteTimeout,
		IdleTimeout:    a.cfg.ServerHTTP.IdleTimeout,
		MaxHeaderBytes: a.cfg.ServerHTTP.MaxHeaderBytes,
	}

	go func() {
		logger.Info().Msg("start http successfully")
		if err = a.httpServer.Serve(listener); err != nil {
			logger.Fatal().Err(err).Msg("failed to start HTTP")
		}
	}()

	return nil
}



Или лучше раскидать данные сервисы по веткам проекта и импортировать в главный?
  • Вопрос задан
  • 122 просмотра
Подписаться 1 Простой 12 комментариев
Пригласить эксперта
Ответы на вопрос 2
@d-stream
Готовые решения - не подаю, но...
микросервисы - сущность характерная для более-менее активных нагрузок, ибо сама идея была на уровне горизонтального масштабирования.
То есть грубо если на 100500 запросов масштабировать монолит - то надо xN экземпляров монолита с такой же кратностью ресурсов, а если прикладная система рубится на те самые функциональные части (микросервисы), то оказывается что надо разное количество экземпляров для разных "кусков" (микросервисов), что на больших цифрах и даст профит.
Естественно у этого профита есть и обратная сторона - передача данных между микросервисами это совсем не то же самое что передача параметров в вызов метода (даже по значению) и т.п.

Так что для pet проектов - это всё будет баловством и натягиванием микросервисом за уши.
Но если уж очень захочется - можно и пострадать микросервисами головного мозга и сделать вычурное:
- взять и разделить всё например по api - грубо на каждую crud четвёрку - свой микросервис
- не дать взаимодействовать микросервисам между собой - пусть этим занимается отдельный микросервис-передаст
- аналогично базой - вот пусть с ней работает отдельный микросервис, а остальные ходят к нему (хотя это слегка нарушит концепт "каждому микросервису по своей БД")

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

Монстр - готов)
Ответ написан
Комментировать
Maksclub
@Maksclub
maksfedorov.ru
Микросервисы — не только для нагрузки, а также инструмент менеджмента разработки.

Что это значит?
Это значит, что есть большая команда и наличие одной кодовой базы — есть точка препятствия, когда одна команда зависит от другой, а нужно делать быстро и независимо. Иными словами пример: есть в Яндекс.Такси 10 команд, в одной кодовой базе ни будут толкаться и зависеть друг от друга, зависимости общие, разрезание на микросервисы помогают разделить эту работу, выделить скоуп и каждая команда теперь может работать со своей скоростью, своими логами, своим релизным циклом и не зависеть от других...
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы
19 февр. 2025, в 21:51
50000 руб./за проект
19 февр. 2025, в 21:33
150000 руб./за проект
19 февр. 2025, в 21:08
30000 руб./за проект