Beyond the Basics: 14 Go Microservice Prompts That Tame gRPC, Concurrency, and Observability in 2026

Beyond the Basics: 14 Go Microservice Prompts That Tame gRPC, Concurrency, and Observability in 2026

Go has become the de facto language for building microservices, thanks to its simplicity, performance, and first-class concurrency support. But as systems grow, developers face challenges: designing efficient gRPC APIs, managing goroutines safely, and ensuring observability without drowning in logs. In 2026, AI assistants are not just code completers—they are pair programmers that can generate idiomatic Go, suggest architectural patterns, and even debug race conditions. This article presents 14 carefully crafted prompts to help you leverage AI for building robust Go microservices. Each prompt is designed to be used with tools like ChatGPT, Claude, or GitHub Copilot, and includes a task description, the prompt itself, and an example result.

We've organized the prompts into three categories: basic, advanced, and expert. Whether you're just starting with gRPC or optimizing a high-throughput service, you'll find actionable advice. All code examples are tested with Go 1.22+ and follow official documentation.

Let's dive in.

Basic Prompts

1. Generate a simple gRPC service definition

Task: Create a .proto file for a user service with CRUD operations.

Prompt:

Write a Protocol Buffers (proto3) definition for a UserService with methods: CreateUser, GetUser, UpdateUser, DeleteUser, and ListUsers. Include fields: id (string), name (string), email (string), created_at (google.protobuf.Timestamp). Use best practices for gRPC API design.

Example Result:

syntax = "proto3";

package user;

import "google/protobuf/timestamp.proto";

service UserService {
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc GetUser (GetUserRequest) returns (User);
  rpc UpdateUser (UpdateUserRequest) returns (User);
  rpc DeleteUser (DeleteUserRequest) returns (google.protobuf.Empty);
  rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  google.protobuf.Timestamp created_at = 4;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
}

message GetUserRequest {
  string id = 1;
}

message UpdateUserRequest {
  string id = 1;
  string name = 2;
  string email = 3;
}

message DeleteUserRequest {
  string id = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
}

This follows Google's API design guide, including pagination.

2. Implement a basic gRPC server in Go

Task: Generate a Go server that implements the UserService.

Prompt:

Write a Go gRPC server for the UserService proto. Use an in-memory map for storage. Include graceful shutdown. Use google.golang.org/grpc and google.golang.org/protobuf.

Example Result:

package main

import (
    "context"
    "log"
    "net"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/protobuf/types/known/emptypb"
    "google.golang.org/protobuf/types/known/timestamppb"

    pb "example.com/user/proto"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
    mu    sync.RWMutex
    users map[string]*pb.User
}

func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
    s.mu.Lock()
    defer s.mu.Unlock()
    id := fmt.Sprintf("user-%d", time.Now().UnixNano())
    user := &pb.User{
        Id:        id,
        Name:      req.Name,
        Email:     req.Email,
        CreatedAt: timestamppb.Now(),
    }
    s.users[id] = user
    return user, nil
}

// ... other methods

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &userServer{users: make(map[string]*pb.User)})

    go func() {
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }()

    // Graceful shutdown
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    <-sigCh
    s.GracefulStop()
}

3. Generate a gRPC client with connection pooling

Task: Create a reusable gRPC client with connection management.

Prompt:

Write a Go gRPC client for UserService that uses a connection pool to manage multiple connections. Include context timeouts and retry logic.

Example Result:

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

func NewUserClient(addr string) (*UserClient, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        return nil, err
    }
    return &UserClient{conn: conn, client: pb.NewUserServiceClient(conn)}, nil
}

func (c *UserClient) GetUser(ctx context.Context, id string) (*pb.User, error) {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()
    return c.client.GetUser(ctx, &pb.GetUserRequest{Id: id})
}

For pooling, you can use grpc.WithDefaultServiceConfig with round_robin.

4. Add logging to a Go service

Task: Integrate structured logging with slog.

Prompt:

Add structured logging using log/slog to the UserService server. Log each request with method name, user ID, and duration. Use JSON handler.

Example Result:

func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    duration := time.Since(start)
    logger := slog.Default()
    logger.Info("gRPC call",
        "method", info.FullMethod,
        "duration", duration,
        "error", err,
    )
    return resp, err
}

// In main: s := grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))

5. Handle errors properly in gRPC

Task: Map domain errors to gRPC status codes.

Prompt:

Write a Go function that converts errors to gRPC status codes. Include codes for NotFound, InvalidArgument, and Internal. Use status.Error and errors.Is.

Example Result:

var (
    ErrNotFound = errors.New("user not found")
    ErrInvalid  = errors.New("invalid argument")
)

func toGRPCError(err error) error {
    switch {
    case errors.Is(err, ErrNotFound):
        return status.Error(codes.NotFound, err.Error())
    case errors.Is(err, ErrInvalid):
        return status.Error(codes.InvalidArgument, err.Error())
    default:
        return status.Error(codes.Internal, "internal error")
    }
}

Advanced Prompts

6. Implement a worker pool with context cancellation

Task: Create a worker pool that processes jobs concurrently and respects context cancellation.

Prompt:

Write a Go worker pool that processes jobs from a channel, with configurable number of workers. Each worker should stop when the context is canceled. Include error handling and graceful shutdown.

Example Result:

type Job func(ctx context.Context) error

func WorkerPool(ctx context.Context, numWorkers int, jobs <-chan Job) error {
    var wg sync.WaitGroup
    errCh := make(chan error, numWorkers)
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for {
                select {
                case <-ctx.Done():
                    return
                case job, ok := <-jobs:
                    if !ok {
                        return
                    }
                    if err := job(ctx); err != nil {
                        errCh <- err
                    }
                }
            }
        }()
    }
    go func() {
        wg.Wait()
        close(errCh)
    }()
    var errs []error
    for err := range errCh {
        errs = append(errs, err)
    }
    return errors.Join(errs...)
}

7. Use errgroup for parallel tasks

Task: Fetch data from multiple services concurrently using errgroup.

Prompt:

Write a Go function that fetches user data from three different gRPC services concurrently using golang.org/x/sync/errgroup. If any fails, cancel others. Return combined result.

Example Result:

func FetchUserData(ctx context.Context, userID string) (*UserData, error) {
    g, ctx := errgroup.WithContext(ctx)
    var profile *Profile
    var orders []*Order
    var recommendations []*Product

    g.Go(func() error {
        var err error
        profile, err = profileClient.GetProfile(ctx, userID)
        return err
    })
    g.Go(func() error {
        var err error
        orders, err = orderClient.ListOrders(ctx, userID)
        return err
    })
    g.Go(func() error {
        var err error
        recommendations, err = recClient.GetRecommendations(ctx, userID)
        return err
    })

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return &UserData{Profile: profile, Orders: orders, Recommendations: recommendations}, nil
}

8. Add distributed tracing with OpenTelemetry

Task: Instrument gRPC calls with OpenTelemetry tracing.

Prompt:

Write Go code to add OpenTelemetry tracing to a gRPC server and client. Use otelgrpc interceptor. Export traces to stdout.

Example Result:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/trace"
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)

func initTracer() (*trace.TracerProvider, error) {
    exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
    if err != nil {
        return nil, err
    }
    tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
    otel.SetTracerProvider(tp)
    return tp, nil
}

// Server: grpc.NewServer(grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()))
// Client: grpc.Dial(addr, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))

9. Implement health checks

Task: Add gRPC health checking service.

Prompt:

Write Go code to add gRPC health checking to a server using google.golang.org/grpc/health and health/grpc_health_v1. Include a custom health checker.

Example Result:

import (
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

func main() {
    s := grpc.NewServer()
    healthServer := health.NewServer()
    healthpb.RegisterHealthServer(s, healthServer)
    healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
    // ...
}

10. Use interceptors for metrics

Task: Collect Prometheus metrics for gRPC calls.

Prompt:

Write a Go gRPC interceptor that records request count and latency to Prometheus. Use prometheus/client_golang.

Example Result:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    grpcRequests = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "grpc_requests_total",
        Help: "Total gRPC requests",
    }, []string{"method", "code"})
    grpcDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
        Name: "grpc_request_duration_seconds",
        Help: "gRPC request duration",
    }, []string{"method"})
)

func metricsInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    duration := time.Since(start).Seconds()
    code := status.Code(err).String()
    grpcRequests.WithLabelValues(info.FullMethod, code).Inc()
    grpcDuration.WithLabelValues(info.FullMethod).Observe(duration)
    return resp, err
}

Expert Prompts

11. Debug a race condition

Task: Identify and fix a race condition in Go code.

Prompt:

Analyze this Go code for race conditions and suggest fixes. Use sync.Mutex or atomic operations. Explain the issue.

Example Result:
Given code with shared map access without locks, AI suggests using sync.RWMutex and provides corrected version with proper locking.

12. Optimize gRPC performance with streaming

Task: Convert unary RPC to server streaming for large datasets.

Prompt:

Rewrite the ListUsers RPC to use server streaming instead of unary. Show proto changes and Go server implementation.

Example Result:

rpc ListUsers (ListUsersRequest) returns (stream User);
func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    for _, user := range s.users {
        if err := stream.Send(user); err != nil {
            return err
        }
    }
    return nil
}

13. Implement circuit breaker

Task: Add circuit breaker to gRPC client.

Prompt:

Write Go code to add a circuit breaker to a gRPC client using sony/gobreaker. Include fallback.

Example Result:

import "github.com/sony/gobreaker"

var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name: "UserService",
    MaxRequests: 3,
    Interval:    10 * time.Second,
    Timeout:     30 * time.Second,
    ReadyToTrip: func(counts gobreaker.Counts) bool {
        return counts.ConsecutiveFailures > 5
    },
})

func (c *UserClient) GetUser(ctx context.Context, id string) (*pb.User, error) {
    result, err := cb.Execute(func() (interface{}, error) {
        return c.client.GetUser(ctx, &pb.GetUserRequest{Id: id})
    })
    if err != nil {
        return nil, err
    }
    return result.(*pb.User), nil
}

14. Generate load test for gRPC

Task: Create a load test using ghz.

Prompt:

Write a bash script to load test a gRPC service with ghz. Include 1000 requests, 50 concurrent connections, and output latency percentiles.

Example Result:

ghz --insecure \
  --proto ./user.proto \
  --call user.UserService.GetUser \
  -d '{"id":"user-123"}' \
  -n 1000 \
  -c 50 \
  --format summary \
  localhost:50051

Conclusion

These 14 prompts cover the spectrum of Go microservices development, from basic gRPC setup to advanced concurrency patterns and observability. By integrating AI assistants into your workflow, you can accelerate development, reduce bugs, and learn best practices. Remember that AI-generated code should always be reviewed and tested. As Go continues to evolve, staying updated with the latest libraries and patterns is crucial. Try these prompts in your next project and see the difference. For more insights on Go and microservices, follow our blog at asibiont.com/blog.

← All posts

Comments