github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/cmd/server/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"net/http"
     8  	"os"
     9  	"time"
    10  
    11  	"cloud.google.com/go/profiler"
    12  	"github.com/99designs/gqlgen/graphql/handler"
    13  	"github.com/99designs/gqlgen/graphql/playground"
    14  	"github.com/GoogleCloudPlatform/berglas/pkg/berglas"
    15  	"github.com/rollbar/rollbar-go"
    16  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    17  	"go.opentelemetry.io/otel"
    18  	"goji.io/v3"
    19  	"goji.io/v3/pat"
    20  
    21  	"github.com/oinume/lekcije/backend/domain/config"
    22  	"github.com/oinume/lekcije/backend/event_logger"
    23  	"github.com/oinume/lekcije/backend/infrastructure/ga_measurement"
    24  	"github.com/oinume/lekcije/backend/infrastructure/gcp"
    25  	"github.com/oinume/lekcije/backend/infrastructure/mysql"
    26  	"github.com/oinume/lekcije/backend/infrastructure/open_telemetry"
    27  	interfaces "github.com/oinume/lekcije/backend/interface"
    28  	"github.com/oinume/lekcije/backend/interface/graphql"
    29  	"github.com/oinume/lekcije/backend/interface/graphql/generated"
    30  	"github.com/oinume/lekcije/backend/interface/graphql/resolver"
    31  	interfaces_http "github.com/oinume/lekcije/backend/interface/http"
    32  	"github.com/oinume/lekcije/backend/logger"
    33  	"github.com/oinume/lekcije/backend/model"
    34  	"github.com/oinume/lekcije/backend/registry"
    35  )
    36  
    37  const (
    38  	maxDBConnections = 10
    39  	serviceName      = "lekcije"
    40  )
    41  
    42  func main() {
    43  	ctx := context.Background()
    44  	gae := os.Getenv("GAE_APPLICATION")
    45  	if gae != "" {
    46  		for _, env := range []string{"MYSQL_USER", "MYSQL_PASSWORD", "MYSQL_HOST"} {
    47  			if os.Getenv(env) == "" {
    48  				log.Fatalf("Environment variable %q is required", env)
    49  			}
    50  			if err := berglas.Replace(ctx, env); err != nil {
    51  				log.Fatalf("berglas.Replace failed: %v", err)
    52  			}
    53  		}
    54  	}
    55  	config.MustProcessDefault()
    56  	configVars := config.DefaultVars
    57  	port := configVars.HTTPPort
    58  
    59  	tracerProvider, err := open_telemetry.NewTracerProvider("server", config.DefaultVars)
    60  	if err != nil {
    61  		log.Fatalf("NewTraceProvider failed: %v", err)
    62  	}
    63  	defer func() {
    64  		if err := tracerProvider.Shutdown(context.Background()); err != nil {
    65  			log.Fatal(err)
    66  		}
    67  	}()
    68  	otel.SetTracerProvider(tracerProvider)
    69  
    70  	if config.DefaultVars.EnableStackdriverProfiler {
    71  		credential, err := gcp.WithCredentialsJSONFromBase64String(configVars.GCPServiceAccountKey)
    72  		if err != nil {
    73  			log.Fatalf("WithCredentialsJSONFromBase64String failed: %v", err)
    74  		}
    75  
    76  		// TODO: Move to gcp package
    77  		if err := profiler.Start(profiler.Config{
    78  			ProjectID:      configVars.GCPProjectID,
    79  			Service:        serviceName,
    80  			ServiceVersion: "1.0.0", // TODO: release version?
    81  			DebugLogging:   false,
    82  		}, credential); err != nil {
    83  			log.Fatalf("Stackdriver profiler.Start failed: %v", err)
    84  		}
    85  	}
    86  
    87  	gormDB, err := model.OpenDB(
    88  		configVars.DBURL(),
    89  		maxDBConnections,
    90  		configVars.DebugSQL,
    91  	)
    92  	if err != nil {
    93  		log.Fatalf("model.OpenDB failed: %v", err)
    94  	}
    95  	defer gormDB.Close()
    96  
    97  	accessLogger := logger.NewAccessLogger(os.Stdout)
    98  	appLogger := logger.NewAppLogger(os.Stderr, logger.NewLevel("info")) // TODO: flag
    99  	rollbarClient := rollbar.New(
   100  		configVars.RollbarAccessToken,
   101  		configVars.ServiceEnv,
   102  		configVars.VersionHash,
   103  		"", "/",
   104  	)
   105  	defer rollbarClient.Close()
   106  
   107  	args := &interfaces.ServerArgs{
   108  		AccessLogger:        accessLogger,
   109  		AppLogger:           appLogger,
   110  		DB:                  gormDB.DB(),
   111  		GAMeasurementClient: ga_measurement.NewClient(nil, event_logger.New(accessLogger)),
   112  		GormDB:              gormDB,
   113  		RollbarClient:       rollbarClient,
   114  		SenderHTTPClient: &http.Client{
   115  			Timeout: 5 * time.Second,
   116  		},
   117  	}
   118  
   119  	errors := make(chan error)
   120  	go func() {
   121  		errors <- startHTTPServer(port, args)
   122  	}()
   123  
   124  	for err := range errors {
   125  		log.Fatal(err)
   126  	}
   127  }
   128  
   129  func startHTTPServer(port int, args *interfaces.ServerArgs) error {
   130  	mux := goji.NewMux()
   131  
   132  	// TODO: graceful shutdown
   133  	errorRecorder := registry.NewErrorRecorderUsecase(args.AppLogger, args.RollbarClient)
   134  	userAPIToken := registry.NewUserAPITokenUsecase(args.DB)
   135  	server := interfaces_http.NewServer(args, errorRecorder, userAPIToken)
   136  	oauthServer := registry.NewOAuthServer(args.AppLogger, args.DB, args.GAMeasurementClient, args.RollbarClient, args.SenderHTTPClient)
   137  	mCountryList := registry.MustNewMCountryList(context.Background(), args.DB)
   138  	server.Setup(mux)
   139  	oauthServer.Setup(mux)
   140  
   141  	gqlResolver := resolver.NewResolver(
   142  		mysql.NewFollowingTeacherRepository(args.DB),
   143  		registry.NewFollowingTeacherUsecase(args.AppLogger, args.DB, mCountryList),
   144  		registry.NewGAMeasurementUsecase(args.GAMeasurementClient),
   145  		mysql.NewNotificationTimeSpanRepository(args.DB),
   146  		registry.NewNotificationTimeSpanUsecase(args.DB),
   147  		mysql.NewTeacherRepository(args.DB),
   148  		mysql.NewUserRepository(args.DB),
   149  		registry.NewUserUsecase(args.DB),
   150  	)
   151  	gqlSchema := generated.NewExecutableSchema(generated.Config{
   152  		Resolvers: gqlResolver,
   153  	})
   154  	gqlServer := handler.NewDefaultServer(gqlSchema)
   155  	gqlMiddleware := graphql.NewMiddleware(args.AppLogger)
   156  	gqlServer.AroundOperations(gqlMiddleware.AroundOperations)
   157  	gqlServer.SetErrorPresenter(gqlMiddleware.ErrorPresenter)
   158  
   159  	const gqlPath = "/graphql"
   160  	mux.Handle(pat.Get(gqlPath), gqlServer)
   161  	mux.Handle(pat.Post(gqlPath), gqlServer)
   162  	if !config.DefaultVars.IsProductionEnv() {
   163  		mux.Handle(pat.Get("/playground"), playground.Handler("GraphQL playground", gqlPath))
   164  	}
   165  
   166  	otelHandler := otelhttp.NewHandler(
   167  		mux,
   168  		"server",
   169  		otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents),
   170  	)
   171  	fmt.Printf("Starting HTTP server on %v\n", port)
   172  	return http.ListenAndServe(fmt.Sprintf(":%d", port), otelHandler)
   173  }