package app import ( "context" "fmt" "net" "net/http" "os" "time" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/jackc/pgx/v5/pgxpool" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/reflection" "route256/loms/internal/app/server" ordersRepository "route256/loms/internal/domain/repository/orders/sqlc" stocksRepository "route256/loms/internal/domain/repository/stocks/sqlc" "route256/loms/internal/domain/service" "route256/loms/internal/infra/config" mw "route256/loms/internal/infra/grpc/middleware" "route256/loms/internal/infra/postgres" pb "route256/pkg/api/loms/v1" ) type App struct { config *config.Config controller *server.Server } func NewApp(configPath string) (*App, error) { c, err := config.LoadConfig(configPath) if err != nil { return nil, fmt.Errorf("unable to load config: %w", err) } log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) zerolog.SetGlobalLevel(zerolog.InfoLevel) if c.Service.LogLevel != "" { level, logErr := zerolog.ParseLevel(c.Service.LogLevel) if logErr != nil { return nil, fmt.Errorf("unknown log level `%s` provided: %w", c.Service.LogLevel, logErr) } zerolog.SetGlobalLevel(level) } log.WithLevel(zerolog.GlobalLevel()).Msgf("using logging level=`%s`", zerolog.GlobalLevel().String()) masterPool, replicaPool, err := getPostgresPools(c) if err != nil { return nil, err } stockRepo := stocksRepository.NewStockRepository(masterPool, replicaPool) orderRepo := ordersRepository.NewOrderRepository(masterPool) txManager := postgres.NewTxManager(masterPool, replicaPool) service := service.NewLomsService(orderRepo, stockRepo, txManager) controller := server.NewServer(service) app := &App{ config: c, controller: controller, } return app, nil } func (app *App) ListenAndServe() error { grpcAddr := fmt.Sprintf("%s:%s", app.config.Service.Host, app.config.Service.GRPCPort) l, err := net.Listen("tcp", grpcAddr) if err != nil { return err } grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( mw.Logging, mw.Validate, ), ) reflection.Register(grpcServer) pb.RegisterLOMSServer(grpcServer, app.controller) go func() { if err = grpcServer.Serve(l); err != nil { log.Fatal().Msgf("failed to serve: %v", err) } }() log.Info().Msgf("Serving grpc loms at grpc://%s", l.Addr()) // Setup HTTP gateway conn, err := grpc.NewClient( grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return fmt.Errorf("grpc.NewClient: %w", err) } gwmux := runtime.NewServeMux() if err = pb.RegisterLOMSHandler(context.Background(), gwmux, conn); err != nil { return fmt.Errorf("pb.RegisterLOMSHandler: %w", err) } gwServer := &http.Server{ Addr: fmt.Sprintf("%s:%s", app.config.Service.Host, app.config.Service.HTTPPort), Handler: gwmux, ReadTimeout: 10 * time.Second, } log.Info().Msgf("Serving http loms at http://%s", gwServer.Addr) return gwServer.ListenAndServe() } func getPostgresPools(c *config.Config) (masterPool, replicaPool *pgxpool.Pool, err error) { masterConn := fmt.Sprintf( "postgresql://%s:%s@%s:%s/%s?sslmode=disable", c.DatabaseMaster.User, c.DatabaseMaster.Password, c.DatabaseMaster.Host, c.DatabaseMaster.Port, c.DatabaseMaster.DBName, ) replicaConn := fmt.Sprintf( "postgresql://%s:%s@%s:%s/%s?sslmode=disable", c.DatabaseReplica.User, c.DatabaseReplica.Password, c.DatabaseReplica.Host, c.DatabaseReplica.Port, c.DatabaseReplica.DBName, ) pools, err := postgres.NewPools(context.Background(), masterConn, replicaConn) if err != nil { return nil, nil, err } if len(pools) != 2 { return nil, nil, fmt.Errorf("got wrong number of pools when establishing postgres connection") } return pools[0], pools[1], nil }