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)
}