Work Directories

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.

design pattern diagram

design pattern diagram

/cmd

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)

/app

Private application, library code, including business logic goes to this folder.

/business

/core

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.

/handlers

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.