Go Services


nself provides excellent support for Go services, enabling you to build high-performance, efficient microservices that integrate seamlessly with your nself stack. With automatic configuration, built-in templates, and production-ready deployment, Go services complement your Node.js and Python services with exceptional speed and resource efficiency.

Overview

Go services in nself provide:

Key Benefits

  • High Performance: Compiled binaries with excellent runtime performance
  • Low Resource Usage: Minimal memory footprint and CPU efficiency
  • Concurrency: Built-in goroutines for handling concurrent operations
  • Fast Startup: Near-instantaneous service startup times
  • Static Compilation: Single binary deployment with no runtime dependencies
  • Strong Typing: Type safety and excellent tooling support

Ideal Use Cases

  • API Gateways: High-throughput request routing and transformation
  • Data Processing: ETL operations and stream processing
  • Real-time Services: WebSocket servers and real-time data feeds
  • CLI Tools: Command-line utilities and automation tools
  • Microservices: Resource-efficient distributed services
  • Performance-Critical APIs: When speed and efficiency are paramount

Getting Started

Enable Go Services

Add Go services to your .env.local:

# Enable Go services
GOLANG_SERVICES=api-gateway,data-processor,websocket-server

# Optional: Specify Go version
GOLANG_VERSION=1.21

# Optional: Enable additional features
GOLANG_BUILD_TAGS=jsoniter,netgo
GOLANG_CGO_ENABLED=0

Generate Service Structure

# Generate Go services
nself build

# Start services
nself up

This creates the following structure:

services/
├── golang/
│   ├── api-gateway/        # HTTP API Gateway
│   │   ├── cmd/
│   │   ├── internal/
│   │   ├── pkg/
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── Dockerfile
│   ├── data-processor/     # Data processing service
│   └── websocket-server/   # WebSocket service
└── shared/
    └── go/                 # Shared Go packages
        ├── database/
        ├── redis/
        └── types/

Service Templates

HTTP API Service

// cmd/main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"

    "github.com/gin-gonic/gin"
    "your-app/internal/handlers"
    "your-app/internal/middleware"
    "your-app/pkg/database"
)

func main() {
    // Initialize database connection
    db, err := database.Connect()
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
    defer db.Close()

    // Setup router
    r := gin.New()
    r.Use(gin.Logger())
    r.Use(gin.Recovery())
    r.Use(middleware.CORS())

    // Health check endpoint
    r.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "healthy"})
    })

    // API routes
    api := r.Group("/api/v1")
    handlers.SetupRoutes(api, db)

    // Graceful shutdown
    srv := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    srv.Shutdown(ctx)
}

Database Integration

// pkg/database/postgres.go
package database

import (
    "database/sql"
    "fmt"
    "os"

    _ "github.com/lib/pq"
)

type DB struct {
    *sql.DB
}

func Connect() (*DB, error) {
    dsn := fmt.Sprintf(
        "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
        os.Getenv("POSTGRES_HOST"),
        os.Getenv("POSTGRES_PORT"),
        os.Getenv("POSTGRES_USER"),
        os.Getenv("POSTGRES_PASSWORD"),
        os.Getenv("POSTGRES_DB"),
    )

    sqlDB, err := sql.Open("postgres", dsn)
    if err != nil {
        return nil, err
    }

    // Connection pool settings
    sqlDB.SetMaxOpenConns(25)
    sqlDB.SetMaxIdleConns(5)
    sqlDB.SetConnMaxLifetime(5 * time.Minute)

    if err := sqlDB.Ping(); err != nil {
        return nil, err
    }

    return &DB{sqlDB}, nil
}

// User represents a user in the system
type User struct {
    ID        string    `json:"id"`
    Email     string    `json:"email"`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    IsActive  bool      `json:"is_active"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func (db *DB) GetUser(id string) (*User, error) {
    user := &User{}
    query := `
        SELECT id, email, first_name, last_name, is_active, created_at, updated_at 
        FROM users WHERE id = $1
    `
    
    err := db.QueryRow(query, id).Scan(
        &user.ID, &user.Email, &user.FirstName, 
        &user.LastName, &user.IsActive, &user.CreatedAt, &user.UpdatedAt,
    )
    if err != nil {
        return nil, err
    }
    
    return user, nil
}

Redis Integration

// pkg/redis/client.go
package redis

import (
    "context"
    "encoding/json"
    "os"
    "time"

    "github.com/go-redis/redis/v8"
)

type Client struct {
    rdb *redis.Client
}

func NewClient() *Client {
    rdb := redis.NewClient(&redis.Options{
        Addr:     os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT"),
        Password: os.Getenv("REDIS_PASSWORD"),
        DB:       0,
    })

    return &Client{rdb: rdb}
}

func (c *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
    data, err := json.Marshal(value)
    if err != nil {
        return err
    }
    
    return c.rdb.Set(ctx, key, data, expiration).Err()
}

func (c *Client) Get(ctx context.Context, key string, dest interface{}) error {
    data, err := c.rdb.Get(ctx, key).Result()
    if err != nil {
        return err
    }
    
    return json.Unmarshal([]byte(data), dest)
}

func (c *Client) Close() error {
    return c.rdb.Close()
}

Advanced Patterns

WebSocket Service

// internal/websocket/hub.go
package websocket

import (
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // Configure for production
    },
}

type Hub struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
}

type Client struct {
    hub  *Hub
    conn *websocket.Conn
    send chan []byte
}

func NewHub() *Hub {
    return &Hub{
        clients:    make(map[*Client]bool),
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
    }
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
            log.Printf("Client connected. Total: %d", len(h.clients))
            
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
                log.Printf("Client disconnected. Total: %d", len(h.clients))
            }
            
        case message := <-h.broadcast:
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Printf("WebSocket upgrade error: %v", err)
        return
    }
    
    client := &Client{hub: h, conn: conn, send: make(chan []byte, 256)}
    client.hub.register <- client
    
    go client.writePump()
    go client.readPump()
}

Microservice Communication

// pkg/grpc/client.go
package grpc

import (
    "context"
    "time"

    "google.golang.org/grpc"
    pb "your-app/proto"
)

type Client struct {
    conn *grpc.ClientConn
    client pb.UserServiceClient
}

func NewClient(address string) (*Client, error) {
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        return nil, err
    }

    client := pb.NewUserServiceClient(conn)
    return &Client{conn: conn, client: client}, nil
}

func (c *Client) GetUser(ctx context.Context, userID string) (*pb.User, error) {
    ctx, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()

    req := &pb.GetUserRequest{UserId: userID}
    return c.client.GetUser(ctx, req)
}

func (c *Client) Close() error {
    return c.conn.Close()
}

Performance Optimization

Connection Pooling

// pkg/http/client.go
package http

import (
    "net/http"
    "time"
)

func NewOptimizedClient() *http.Client {
    transport := &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 100,
        IdleConnTimeout:     90 * time.Second,
        DisableKeepAlives:   false,
    }

    return &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }
}

Memory Management

// internal/handlers/streaming.go
package handlers

import (
    "bufio"
    "net/http"

    "github.com/gin-gonic/gin"
)

func StreamDataHandler(c *gin.Context) {
    c.Header("Content-Type", "application/json")
    c.Header("Transfer-Encoding", "chunked")

    writer := bufio.NewWriter(c.Writer)
    defer writer.Flush()

    // Stream data in chunks to avoid memory buildup
    for i := 0; i < 1000; i++ {
        data := generateData(i)
        writer.WriteString(data + "\n")
        
        // Flush periodically
        if i%100 == 0 {
            writer.Flush()
        }
    }
}

Docker Configuration

Multi-stage Dockerfile

# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Install dependencies
COPY go.mod go.sum ./
RUN go mod download

# Copy source and build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/main.go

# Production stage
FROM alpine:latest

# Install CA certificates for HTTPS requests
RUN apk --no-cache add ca-certificates
WORKDIR /root/

# Copy the binary from builder stage
COPY --from=builder /app/main .

# Create non-root user
RUN adduser -D -s /bin/sh appuser
USER appuser

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

EXPOSE 8080
CMD ["./main"]

Testing

Unit Tests

// internal/handlers/users_test.go
package handlers

import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "your-app/internal/mocks"
)

func TestGetUser(t *testing.T) {
    gin.SetMode(gin.TestMode)
    
    // Setup
    mockDB := &mocks.Database{}
    router := gin.New()
    router.GET("/users/:id", GetUserHandler(mockDB))
    
    // Mock data
    expectedUser := &User{
        ID:    "123",
        Email: "test@example.com",
    }
    mockDB.On("GetUser", "123").Return(expectedUser, nil)
    
    // Test
    req, _ := http.NewRequest("GET", "/users/123", nil)
    recorder := httptest.NewRecorder()
    router.ServeHTTP(recorder, req)
    
    // Assertions
    assert.Equal(t, http.StatusOK, recorder.Code)
    
    var response User
    json.Unmarshal(recorder.Body.Bytes(), &response)
    assert.Equal(t, expectedUser.ID, response.ID)
    assert.Equal(t, expectedUser.Email, response.Email)
    
    mockDB.AssertExpectations(t)
}

CLI Commands

Service Management

# Generate new Go service
nself generate golang --name user-service

# Build Go services
nself build golang --service api-gateway

# Run tests
nself test golang --service api-gateway

# View service logs
nself logs golang --service api-gateway --follow

# Hot reload during development
nself dev golang --service api-gateway

Best Practices

Code Organization

  • Clean Architecture: Separate concerns with clear boundaries
  • Dependency Injection: Use interfaces for testability
  • Error Handling: Wrap errors with context
  • Configuration: Use environment variables

Performance

  • Profile Regularly: Use go tool pprof
  • Avoid Memory Leaks: Close resources properly
  • Use Sync.Pool: For frequently allocated objects
  • Optimize Hot Paths: Profile and optimize critical code

Next Steps

Now that you understand Go services:

Go services provide exceptional performance and efficiency for your nself microservices architecture.