I'm currently working on oof.gg, a scalable Mobile Gaming platform that allows developers to build scalable multiplayer Web and Mobile games without the stress of building infra. To do this, we'll be building various services and clients integrating with the previously built oof.gg-protobufs.
Goals
Build a scalable starting architecture that can provide authentication, verification, APIs, and streaming events for game sessions for oof.gg.
- Leverage envoy for serving up gRPC micro-service instances.
- Use PostgreSQL for persisting games, users, etc...
- Redis for queues, game sessions, shared states, key-value caches, player presence, etc...
- Kafka for logging, scoring, match history, etc...
- Reuse components located in
pkg
across all micro-services. - Use docker compose to lift local environment.
Project Setup
To start, we'll implement a mono-repo for the gRPC services that will serve up the platform. The services will be developed using Go.
pkg/
auth/
auth.go
auth_test.go
config/
config.go
db/
db.go
models.go
middleware/
auth_interceptor.go
stream_interceptor.go
redis/
redis.go
utils/
utils.go
services/
auth-service/
cmd/main.go
server/server.go
Dockerfile
game-service/
cmd/main.go
server/server.go
Dockerfile
docker-compose.yaml
envoy.yaml
go.mod
go.sum
Local Environment
To work locally, I found it best to just set up a docker-compose.yaml with the services, set up a network, and configure Envoy to serve up the gRPC services.
Below is an example docker-compose.yaml
that was used to run the project locally. The Dockerfiles in each service are what you would expect that would be required to set up a Go project for Docker (with the exception that we run the build from the root directory).
version: '3.8'
services:
auth-service:
build:
context: .
dockerfile: services/auth-service/Dockerfile
environment:
- CONFIG_PATH=/app/
ports:
- "50051:50051"
networks:
- oof-network
game-service:
build:
context: .
dockerfile: services/game-service/Dockerfile
environment:
- CONFIG_PATH=/app/
ports:
- "50052:50052"
networks:
- oof-network
envoy:
image: envoyproxy/envoy:v1.18.3
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "8080:8080"
- "9901:9901"
depends_on:
- auth-service
- game-service
- redis
- postgres
networks:
- oof-network
postgres:
image: postgres:latest
ports:
- "5432:5432"
networks:
- oof-network
redis:
image: redis:latest
ports:
- "6379:6379"
networks:
- oof-network
networks:
oof-network:
name: oof-network
driver: bridge
docker-compose.yaml
For Envoy, we will work on a production configuration later – for now, we'll use compose to serve up locally accessible gRPC services.
Setting up a basic gRPC service
As a reminder, we'll be starting with a simple gRPC service that implements the protobuf structures provided by the oof.gg protocol buffers.
package server
import (
"context"
"log"
"net"
"oof-gg/pkg/config"
"github.com/oof-gg/oof-protobufs/generated/go/v1/api/auth"
"google.golang.org/grpc"
)
type AuthService struct {
authpb.UnimplementedAuthServiceServer
Auth auth.AuthInterface
DB *gorm.DB
}
func (s *AuthService) Register(ctx context.Context, req *authpb.RegisterRequest) (*authpb.RegisterResponse) {
// implement Register logic
}
func (s *AuthService) Login(ctx context.Context, req *authpb.LoginRequest) (*authpb.LoginResponse) {
// implement Login logic
}
func (s *AuthService) RefreshToken(ctx context.Context, req *authpb.RefreshTokenRequest) (*authpb.RefreshTokenResponse) {
// implement Refresh Token logic
}
func (s *AuthService) ValidateToken(ctx context.Context, req *authpb.ValidateTokenRequest) (*authpb.ValidateTokenResponse) {
// implement Validate Token logic
}
func Start(cfg *config.Config) error {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
return err
}
s := grpc.NewServer()
auth.RegisterAuthServiceServer(s, &AuthService{})
log.Printf("Server listening at %v", lis.Addr())
// Register reflection service on gRPC server
if cfg.EnableReflection {
reflection.Register(s)
}
return s.Serve(lis)
}
server/server.go
The service reflects the gRPC service definitions in the protocol buffers defined below:
/// Service definition for authentication
service AuthService {
/// User login RPC to generate an access token
rpc Login(LoginRequest) returns (LoginResponse);
/// User registration RPC to create a new user
rpc Register(RegisterRequest) returns (RegisterResponse);
/// RPC to validate an existing token
rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse);
/// RPC to refresh an access token using a refresh token
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
}
Then we set up main.go
to serve up the basic authentication service.
package main
import (
"log"
auth "oof-gg/pkg/auth"
"oof-gg/pkg/config"
"oof-gg/pkg/db"
"oof-gg/services/auth-service/server"
)
func main() {
cfg, err := config.LoadConfig()
log.Default().Println("Loading config")
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Initialize the database
db.InitDB(cfg.Database.DSN)
// Close the database connection when the main function exitss
defer func() {
if err := db.CloseDB(); err != nil {
log.Fatalf("Failed to close database: %v", err)
} else {
log.Println("Database closed")
}
}()
// Bootstrap the database with some initial data
if err := db.Bootstrap(); err != nil {
log.Fatalf("Failed to bootstrap database: %v", err)
} else {
log.Println("Database bootstrapped")
}
// Initialize the auth pkg
instance, err := auth.NewAuth(cfg)
if err != nil {
log.Fatalf("Failed to initialize auth service: %v", err)
} else {
log.Println("Auth service initialized")
}
// Start the server
go func() {
log.Default().Println("Starting server")
authSvc := &server.AuthService{
Auth: instance,
DB: db.GetDB(),
}
if err := server.Start(authSvc, cfg); err != nil {
log.Fatalf("Failed to start server: %v", err)
} else {
log.Printf("Server started successfully")
}
}()
// Block the main from exiting
select {}
}
cmd/main.go
What is not shown here are database and auth pkg
files which have basic initializations and base methods for those packages. From there, you can go ahead and initialize the service.
Initializing & Testing
go run main/cmd.go
Once the your service is initializing, gRPC Reflection can be configured for easy and quick testing services locally to simplify development. Tools like evans or Postman are useful to test the gRPC Service Reflection once they're serving requests.

Up Next
We'll be building out the game services to set up game streams, queues, sessions, broadcasts, and logging using gRPC with Redis and Kafka.
Building gRPC micro-services with Go for oof.gg - Part 1
I'm currently working on oof.gg, a scalable Mobile Gaming platform that allows developers to build scalable multiplayer Web and Mobile games without the stress of building infra.