How to Implement the Service Layer Pattern in Go

The Service Layer Pattern in Go is implemented by defining an interface for business logic and a concrete struct that implements it, separating concerns from your handlers.

How to Implement the Service Layer Pattern in Go

The Service Layer Pattern in Go is implemented by defining an interface for business logic and a concrete struct that implements it, separating concerns from your handlers.

package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"net/http"

	_ "github.com/mattn/go-sqlite3" // Example driver, replace as needed
)

// Define the User struct
type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// Define the interface
//go:generate mockgen -source=service.go -destination=mock_service.go -package=mocks
type UserService interface {
	GetUserByID(ctx context.Context, id string) (*User, error)
}

// Implement the service
type userService struct {
	db *sql.DB
}

func (s *userService) GetUserByID(ctx context.Context, id string) (*User, error) {
	var user User
	// Use QueryRow with context explicitly passed
	err := s.db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, sql.ErrNoRows
		}
		return nil, err
	}
	return &user, nil
}

// Inject the service into your handler
type Handler struct {
	userService UserService
}

func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	user, err := h.userService.GetUserByID(r.Context(), id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			http.Error(w, "User not found", http.StatusNotFound)
			return
		}
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(user)
}