All services in the operator service share the same design pattern and folder structure. "Main" layer here is the entrypoint when the application starts. All service dependencies are initialized in the main.go. Afterwards, every incoming input and outgoing output is handled by the handler layer. This layer can be an HTTP handler of gRPC handler.
Service can have one or more cores depending on how many data models are there in the service. Core holds all the business logic and processes passed by the handler.
Process in the core layer that requires data access to the database will be forwarded to the store which holds the ORM and the access layer to the database. Query is executed in this layer.
Main application for a project. If there are more than one binaries within the project, the directory name for each application should match the name of the executable you want to have (e.g., /cmd/myapp)
Private application, library code, including business logic goes to this folder.
Core application for each API. The subfolders are named after the model related to the API. The data access layer lies inside the subfolder along with the ORM model and the database queries.
Handler is the layer that listens to HTTP request and forward the request to the corresponding core. The framework used to listen for HTTP request is Gin. The handler root file can be found in internal/handlers/v1/api/v1.go. Below is an example for the handler package.
// Package v1 contains the full set of handler functions and routes // supported by the v1 web api. package v1 import ( "os" "cloud.google.com/go/firestore" "github.com/gin-gonic/gin" "github.com/openzipkin/zipkin-go" "gitlab.com/gigaming/igaming/serverless/sample-service/internal/business/sys/kafka" "gitlab.com/gigaming/igaming/serverless/sample-service/internal/business/web/v1/middleware" "gitlab.com/gigaming/igaming/serverless/sample-service/internal/configuration/web" "go.uber.org/zap" ) // Config contains all the mandatory systems required by handlers. type Config struct { Logger *zap.Logger Tracer *zipkin.Tracer DB *firestore.Client Kafka kafka.KafkaClient } //* define gin http router here func GinHttpRouter(cfg Config, httpS *gin.Engine) { // Initialize handlers and the core for each API httpS.GET("/", home) // Group protected routes in a protected variable with JWT Auth Middleware inside protected := httpS.Group("/") protected.Use(middleware.JwtAuthMiddleware(web.RolesGuard)) } // Returning a health check response func home(c *gin.Context) { c.JSON(200, gin.H{ "status": "healthy", "version": os.Getenv("version"), }) }
GinHttpRouter() function assign routes, route groups, and middleware to the Gin Engine before the Engine is set to serve requests in main.go
// Assign routes to Gin Engine in httpS variable httpS := gin.Default() ginConfig := v1.Config{ Logger: log, DB: db, Tracer: tracer, Kafka: kc, } v1.GinHttpRouter(ginConfig, httpS) // Combine gRPC and Gin into a single port server mixedHandler := newHTTPandGRPCMux(httpS, grpcServer) http2Server := &http2.Server{} http1Server := &http.Server{Handler: h2c.NewHandler(mixedHandler, http2Server)} listener, err := net.Listen("tcp", ":"+port) if err != nil { log.Sugar().Fatalf("net.Listen: %v", err) } // Start serving err = http1Server.Serve(listener) if errors.Is(err, http.ErrServerClosed) { log.Sugar().Info("server closed") } else if err != nil { log.Sugar().Fatalf("server: %v", err) } // In use to handle both HTTP and gRPC request through the same port func newHTTPandGRPCMux(httpHandler http.Handler, grpcHandler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.Header.Get("content-type"), "application/grpc") { grpcHandler.ServeHTTP(w, r) return } httpHandler.ServeHTTP(w, r) }) }
After routes are assigned in line 9, the HTTP listener is set to serve connection. Some services need to expose both HTTP and gRPC server in the same port. Since by default the Golang HTTP does not allow this, h2c is used to intercept the connection by combining HTTP and gRPC server into a single handler and forward the request depending on the content-type header.