golang模块化微服务项目布局的Todo后端示例插件go-todo-backend的使用

Golang模块化微服务项目布局的Todo后端示例插件go-todo-backend使用

项目简介

go-todo-backend是一个使用模块化项目布局的Go语言Todo后端示例,适用于产品级微服务开发。它适合作为中型到大型项目的起点。

这个示例使用:

  • Chi作为HTTP路由器
  • REL作为数据库访问层

主要特性:

  • 模块化项目结构
  • 包含完整测试的示例
  • Docker部署支持
  • 兼容todobackend规范

安装与运行

前提条件

  1. 安装mockery用于接口mock生成
  2. 安装rel cli用于数据库迁移

运行步骤

  1. 准备.env文件
cp .env.sample .env
  1. 启动PostgreSQL并创建数据库
docker-compose up -d
  1. 准备数据库schema
rel migrate
  1. 构建并运行
make

项目结构

.
├── api
│   ├── handler
│   │   ├── todos.go
│   │   └── [其他handler].go
│   └── middleware
│       └── [其他中间件].go
├── bin
│   ├── api
│   └── [其他可执行文件]
├── cmd
│   ├── api
│   │   └── main.go
│   └── [其他cmd]
│       └── main.go
├── db
│   ├── schema.sql
│   └── migrations
│       └── [迁移文件]
├── todos
│   ├── todo.go
│   ├── create.go
│   ├── update.go
│   ├── delete.go
│   ├── service.go
│   └── todostest
│       ├── todo.go
│       └── service.go
├── [其他领域]
│   ├── [实体a].go
│   ├── [业务逻辑].go
│   ├── [其他领域]test
│   │   └── service.go
│   └── service.go
└── [其他客户端]
    ├── [实体b].go
    ├── client.go
    └── [其他客户端]test
        └── client.go

代码示例

Todo实体定义 (todos/todo.go)

package todos

import (
	"time"
	
	"github.com/go-rel/rel"
)

// Todo represents todo model.
type Todo struct {
	ID        int
	Title     string
	Completed bool
	CreatedAt time.Time
	UpdatedAt time.Time
}

// TableName for todos.
func (Todo) TableName() string {
	return "todos"
}

// TodoRepository interface for todo repository.
type TodoRepository interface {
	FindAll(todos *[]Todo) error
	Find(todo *Todo, id int) error
	Insert(todo *Todo) error
	Update(todo *Todo) error
	Delete(todo *Todo) error
}

Todo服务实现 (todos/service.go)

package todos

import (
	"context"
	
	"github.com/go-rel/rel"
)

type service struct {
	repository rel.Repository
}

// NewService creates new todo service.
func NewService(repository rel.Repository) *service {
	return &service{
		repository: repository,
	}
}

// List returns all todos.
func (s *service) List(ctx context.Context, todos *[]Todo) error {
	return s.repository.FindAll(ctx, todos)
}

// Get returns single todo by id.
func (s *service) Get(ctx context.Context, todo *Todo, id int) error {
	return s.repository.Find(ctx, todo, rel.Eq("id", id))
}

// Create creates new todo.
func (s *service) Create(ctx context.Context, todo *Todo) error {
	return s.repository.Insert(ctx, todo)
}

// Update updates existing todo.
func (s *service) Update(ctx context.Context, todo *Todo) error {
	return s.repository.Update(ctx, todo)
}

// Delete deletes existing todo.
func (s *service) Delete(ctx context.Context, todo *Todo) error {
	return s.repository.Delete(ctx, todo)
}

HTTP处理器 (api/handler/todos.go)

package handler

import (
	"net/http"
	"strconv"
	
	"github.com/Fs02/go-todo-backend/todos"
	"github.com/go-chi/chi"
	"github.com/go-chi/render"
)

// TodoHandler handles todo http requests.
type TodoHandler struct {
	service *todos.Service
}

// NewTodoHandler returns new todo handler.
func NewTodoHandler(service *todos.Service) *TodoHandler {
	return &TodoHandler{
		service: service,
	}
}

// List handles GET /todos
func (h *TodoHandler) List(w http.ResponseWriter, r *http.Request) {
	var todos []todos.Todo
	if err := h.service.List(r.Context(), &todos); err != nil {
		render.Render(w, r, ErrInternalServer(err))
		return
	}
	
	render.JSON(w, r, todos)
}

// Get handles GET /todos/{id}
func (h *TodoHandler) Get(w http.ResponseWriter, r *http.Request) {
	var todo todos.Todo
	id, _ := strconv.Atoi(chi.URLParam(r, "id"))
	
	if err := h.service.Get(r.Context(), &todo, id); err != nil {
		render.Render(w, r, ErrNotFound)
		return
	}
	
	render.JSON(w, r, todo)
}

// Create handles POST /todos
func (h *TodoHandler) Create(w http.ResponseWriter, r *http.Request) {
	var todo todos.Todo
	
	if err := render.DecodeJSON(r.Body, &todo); err != nil {
		render.Render(w, r, ErrBadRequest(err))
		return
	}
	
	if err := h.service.Create(r.Context(), &todo); err != nil {
		render.Render(w, r, ErrInternalServer(err))
		return
	}
	
	render.Status(r, http.StatusCreated)
	render.JSON(w, r, todo)
}

领域(domain)与客户端(client)的区别

领域和客户端文件夹非常相似,区别在于客户端文件夹实际上不实现任何业务逻辑(服务),而是调用任何内部/外部API来处理领域实体的客户端。


更多关于golang模块化微服务项目布局的Todo后端示例插件go-todo-backend的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang模块化微服务项目布局的Todo后端示例插件go-todo-backend的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go-Todo-Backend: 模块化微服务Todo后端示例

下面我将介绍一个基于Go语言的模块化微服务Todo后端项目布局示例,并提供关键代码实现。

项目结构

go-todo-backend/
├── cmd/
│   └── main.go          # 应用入口
├── internal/
│   ├── config/          # 配置管理
│   ├── controller/      # HTTP控制器
│   ├── domain/          # 核心业务模型
│   ├── repository/      # 数据访问层
│   ├── service/         # 业务逻辑层
│   └── server/          # HTTP服务器配置
├── pkg/
│   ├── database/        # 数据库连接
│   └── middleware/      # 中间件
├── api/                 # API文档/OpenAPI规范
├── migrations/          # 数据库迁移文件
├── go.mod
└── go.sum

核心代码实现

1. 领域模型 (internal/domain/todo.go)

package domain

import "time"

type Todo struct {
    ID          uint      `json:"id"`
    Title       string    `json:"title" validate:"required"`
    Description string    `json:"description"`
    Completed   bool      `json:"completed"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   time.Time `json:"updated_at"`
}

type TodoRepository interface {
    GetAll() ([]Todo, error)
    GetByID(id uint) (*Todo, error)
    Create(todo *Todo) error
    Update(id uint, todo *Todo) error
    Delete(id uint) error
}

2. 服务层 (internal/service/todo_service.go)

package service

import (
    "go-todo-backend/internal/domain"
    "go-todo-backend/internal/repository"
)

type TodoService struct {
    repo repository.TodoRepository
}

func NewTodoService(repo repository.TodoRepository) *TodoService {
    return &TodoService{repo: repo}
}

func (s *TodoService) GetAllTodos() ([]domain.Todo, error) {
    return s.repo.GetAll()
}

func (s *TodoService) GetTodoByID(id uint) (*domain.Todo, error) {
    return s.repo.GetByID(id)
}

func (s *TodoService) CreateTodo(todo *domain.Todo) error {
    return s.repo.Create(todo)
}

func (s *TodoService) UpdateTodo(id uint, todo *domain.Todo) error {
    return s.repo.Update(id, todo)
}

func (s *TodoService) DeleteTodo(id uint) error {
    return s.repo.Delete(id)
}

3. HTTP控制器 (internal/controller/todo_controller.go)

package controller

import (
    "net/http"
    "strconv"
    
    "github.com/gin-gonic/gin"
    "go-todo-backend/internal/domain"
    "go-todo-backend/internal/service"
)

type TodoController struct {
    service *service.TodoService
}

func NewTodoController(service *service.TodoService) *TodoController {
    return &TodoController{service: service}
}

func (c *TodoController) GetTodos(ctx *gin.Context) {
    todos, err := c.service.GetAllTodos()
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    ctx.JSON(http.StatusOK, todos)
}

func (c *TodoController) GetTodo(ctx *gin.Context) {
    id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
        return
    }
    
    todo, err := c.service.GetTodoByID(uint(id))
    if err != nil {
        ctx.JSON(http.StatusNotFound, gin.H{"error": "Todo not found"})
        return
    }
    
    ctx.JSON(http.StatusOK, todo)
}

// 其他CRUD方法类似...

4. 主程序入口 (cmd/main.go)

package main

import (
    "log"
    
    "github.com/gin-gonic/gin"
    "go-todo-backend/internal/config"
    "go-todo-backend/internal/controller"
    "go-todo-backend/internal/repository"
    "go-todo-backend/internal/server"
    "go-todo-backend/internal/service"
    "go-todo-backend/pkg/database"
)

func main() {
    // 加载配置
    cfg, err := config.Load()
    if err != nil {
        log.Fatalf("Failed to load config: %v", err)
    }
    
    // 初始化数据库
    db, err := database.NewPostgresConnection(cfg.DB)
    if err != nil {
        log.Fatalf("Failed to connect to database: %v", err)
    }
    
    // 自动迁移
    if err := database.Migrate(db); err != nil {
        log.Fatalf("Failed to migrate database: %v", err)
    }
    
    // 初始化依赖
    todoRepo := repository.NewTodoRepository(db)
    todoService := service.NewTodoService(todoRepo)
    todoController := controller.NewTodoController(todoService)
    
    // 创建Gin路由
    router := gin.Default()
    
    // 注册路由
    api := router.Group("/api")
    {
        api.GET("/todos", todoController.GetTodos)
        api.GET("/todos/:id", todoController.GetTodo)
        api.POST("/todos", todoController.CreateTodo)
        api.PUT("/todos/:id", todoController.UpdateTodo)
        api.DELETE("/todos/:id", todoController.DeleteTodo)
    }
    
    // 启动服务器
    srv := server.NewServer(cfg.Server, router)
    if err := srv.Run(); err != nil {
        log.Fatalf("Failed to start server: %v", err)
    }
}

使用说明

  1. 克隆项目后,安装依赖:
go mod tidy
  1. 配置数据库连接信息:
cp config.example.yaml config.yaml
# 然后编辑config.yaml文件
  1. 运行项目:
go run cmd/main.go

API端点

  • GET /api/todos - 获取所有Todo项
  • GET /api/todos/:id - 获取特定Todo项
  • POST /api/todos - 创建新的Todo项
  • PUT /api/todos/:id - 更新Todo项
  • DELETE /api/todos/:id - 删除Todo项

扩展建议

  1. 添加JWT认证中间件
  2. 实现分页和过滤功能
  3. 添加Swagger文档
  4. 集成测试套件
  5. 添加Docker支持

这个示例展示了如何构建一个模块化的Go微服务后端,遵循清晰的层次结构,便于维护和扩展。

回到顶部