1. 前言
Go是一种开发者喜爱的编程语言,最近几年逐渐流行起来。其用途趋向于网络编程和云计算方向。但是,由于其语言特性的问题,长期以来它缺乏通用的设计模式。因此,本文将提供一个示例以展示如何使用Go语言进行代码架构设计实践。
2. 架构设计基础
在开始进行代码架构设计之前,我们需要了解一些基础概念。
2.1 什么是架构设计?
架构设计是一种强调系统结构组成和模块之间交互方式的过程,其中,系统结构的组成和交互方式决定了应用的性能、可扩展性、可维护性、安全性和可用性。
2.2 架构设计的目标
我们进行架构设计的目标是为了:
提高系统性能
提高系统可扩展性
提高系统可维护性
提高系统安全性
提高系统可用性
2.3 常用的软件体系架构
常用的软件体系架构有以下几种:
Monolithic(单体)
Microservices(微服务)
Service-Oriented Architecture(面向服务架构)
Event-Driven Architecture(事件驱动架构)
2.4 代码分层架构
在构建Web应用程序时,最常见的代码架构是分层架构。通常,分层架构包括以下几个层次:
API层:接受请求并响应结果,通常应该很轻量级且无状态
服务层:驱动应用程序并负责业务逻辑
数据访问层:处理数据存储和检索
数据模型:应用程序相关的领域对象结构,通常是根据数据库表格生成的
3. Go语言实现分层架构设计
下面我们将演示一个分层架构设计的样例。
3.1 项目结构
首先,我们需要先创建项目的基本结构,如下所示:
- api/
- cmd/
- config/
- deployment/
- internal/
- domain/
- repository/
- service/
- transport/
- pkg/
- vendor/
3.2 代码解析
接下来我们针对具体的项目代码进行解析和讲解。
3.2.1 cmd
cmd目录下包含了我们的可执行程序代码。我们将需要一个main.go文件,我们可以在其中引入我们的HTTP API代码。
package main
import (
"log"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "app",
Short: "app does some cool stuff",
Long: "A longer description that spans multiple lines and likely contains examples",
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Println(err)
os.Exit(1)
}
}
3.2.2 domain
domain层包含了应用程序的核心业务逻辑。在这里,我们将实现我们的模型和处理它们的方法。在本例中,我们将创建一个名为book的模型,并定义了一些方法。
package domain
import "context"
type Book struct {
ID int
Title string
Author string
Price float64
}
type BookRepository interface {
GetByID(context.Context, int) (*Book, error)
Save(context.Context, *Book) error
}
type BookService interface {
GetBook(context.Context, int) (*Book, error)
CreateBook(context.Context, string, string, float64) error
}
type bookService struct {
repo BookRepository
}
func (s *bookService) GetBook(ctx context.Context, id int) (*Book, error) {
return s.repo.GetByID(ctx, id)
}
func (s *bookService) CreateBook(ctx context.Context, title string, author string, price float64) error {
book := &Book{Title: title, Author: author, Price: price}
return s.repo.Save(ctx, book)
}
func NewBookService(repo BookRepository) BookService {
return &bookService{repo: repo}
}
3.2.3 repository
数据访问层是对数据的操作,这里我们将使用MySQL做为我们的数据存储。在repository层,我们需要定义相应的接口,并提供实现如何到底存储代码。在我们的bookRepository中,我们将定义用于获取和保存book结构体的方法。
package repository
import (
"context"
"database/sql"
"github.com/example/app/internal/domain"
)
type BookRepository interface {
GetByID(context.Context, int) (*domain.Book, error)
Save(context.Context, *domain.Book) error
}
type bookRepository struct {
db *sql.DB
}
func (r *bookRepository) GetByID(ctx context.Context, id int) (*domain.Book, error) {
book := &domain.Book{}
err := r.db.QueryRowContext(ctx,
"SELECT id, title, author, price FROM book WHERE id=?",
id).Scan(&book.ID, &book.Title, &book.Author, &book.Price)
if err != nil {
return nil, err
}
return book, nil
}
func (r *bookRepository) Save(ctx context.Context, book *domain.Book) error {
res, err := r.db.ExecContext(ctx,
"INSERT INTO book (title, author, price) VALUES (?, ?, ?)",
book.Title, book.Author, book.Price)
if err != nil {
return err
}
id, err := res.LastInsertId()
if err != nil {
return err
}
book.ID = int(id)
return nil
}
func NewBookRepository(db *sql.DB) BookRepository {
return &bookRepository{db}
}
3.2.4 service
service层是我们的业务逻辑实现,它是我们应用程序的主要工作部分,并负责处理数据访问层和API层之间的交互。在这里,我们将定义我们的API endpoints,并将它们与我们的repo和domain进行连接。
package service
import (
"encoding/json"
"net/http"
"strconv"
"github.com/example/app/internal/domain"
)
type BookService interface {
GetBook(domain.ID) (domain.Book, error)
CreateBook(domain.Book) error
}
type Handler struct {
BookService BookService
}
func (h *Handler) GetBook(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
book, err := h.BookService.GetBook(domain.ID(id))
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(book)
}
func (h *Handler) CreateBook(w http.ResponseWriter, r *http.Request) {
var book domain.Book
err := json.NewDecoder(r.Body).Decode(&book)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
err = h.BookService.CreateBook(book)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
}
3.2.5 transport
transport层是我们的API层,负责处理HTTP请求和响应的构建。我们使用gorilla/mux包来轻松实现路由。
package transport
import (
"database/sql"
"log"
"net/http"
"github.com/example/app/internal/domain"
"github.com/example/app/internal/repository"
"github.com/example/app/internal/service"
"github.com/gorilla/mux"
_ "github.com/mattn/go-sqlite3"
)
func Init() http.Handler {
db, err := sql.Open("sqlite3", "./book.db")
if err != nil {
log.Fatal(err)
}
bookRepo := repository.NewBookRepository(db)
bookService := service.NewBookService(bookRepo)
handler := &service.Handler{BookService: bookService}
router := mux.NewRouter()
router.HandleFunc("/book", handler.GetBook).Methods("GET")
router.HandleFunc("/book", handler.CreateBook).Methods("POST")
return router
}
3.3 总结
在本文中,我们讨论了如何为我们的Go应用程序创建一个适用于分层架构的基础结构。我们介绍了一些基本概念和基本架构,并提供了示例代码,以帮助您快速启动您的下一个项目。如果您有任何问题或建议,请随时在下面的评论中提出。