使用MySQL,Gorm,Echo和Clean Architecture构建CRUD应用程序
#go #app #crud #cleanarchitecture

介绍

在本文中,我将使用Clean Architecture创建一个具有CRUD(创建,读取,更新,删除)功能的API。我将使用mySQL作为我们的数据库,回声作为我们的框架,将Gorm作为ORM。

我会建造什么

我将使用创建,读取,(更新)和删除功能创建一个API。更新功能尚未实现,因此请随时添加它!

目标听众

已经建立了GO环境并想创建一个简单的API的人。

使用的技术

Table of tech stack and type

什么是干净的建筑?

清洁架构以下图是众所周知的:

Diagram of Clean Architecture

清洁体系结构的目的是关注点的分离,这是通过关注层之间的依赖性来实现的。这种分离导致改进的代码可读性和更强大的设计。有关清洁架构的好处和细节的更多信息,请参考参考文章。
在上图中,箭头从外层到内层指示依赖性方向。依赖性是从外层到内层的,但反之亦然。
换句话说,您可以调用外层内部层中声明的项目,但是您无法调用内层外层中声明的项目。
在本文中,我将在关注依赖方向的同时介绍代码。

端点

每个功能的端点如下:

GET: /users
POST: /users
DELETE: /users/:id

目录结构

Directory Structure

可以将干净体系结构的层与目录结构对齐,如下所示:

Table of Directory name and layer

领域

在域层中,定义了实体。就像在中心一样,可以从任何一层调用。

/src/domain/user.go

package domain

type User struct {
    ID   int    `json:"id" gorm:"primary_key"`
    Name string `json:"name"`
} 

在此示例中,我将创建一个具有ID和名称列的用户,并将ID集作为主要键。

关于json:“ id” gorm:“ prientar_key”,json:“ id”用于json映射,而gorm:“ prientar_key”用于在Gorm中标记模型。您还可以定义其他SQL表属性,例如NOT NULL,唯一和默认值。
参考:Gorm声明模型

基础设施

最外层的基础架构涉及与外部组件相互作用的应用程序部分。在这种情况下,我在此处定义数据库连接和路由器。

由于它是最外层的一层,因此可以在不知道其他任何层的情况下被调用。

/src/infrastructure/sqlhandler.go

package infrastructure

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"

    "echoSample/src/interfaces/database"
)

type SqlHandler struct {
    db *gorm.DB
}

func NewSqlHandler() database.SqlHandler {
    dsn := "root:password@tcp(127.0.0.1:3306)/go_sample?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err.Error)
    }
    sqlHandler := new(SqlHandler)
    sqlHandler.db = db
    return sqlHandler
}

func (handler *SqlHandler) Create(obj interface{}) {
    handler.db.Create(obj)
}

func (handler *SqlHandler) FindAll(obj interface{}) {
    handler.db.Find(obj)
}

func (handler *SqlHandler) DeleteById(obj interface{}, id string) {
    handler.db.Delete(obj, id)
}

对于数据库连接,官方文档很有帮助:
blue.io

接下来是路由。在此示例中,我使用Echo Web框架。官方文档也对此很有帮助,这是我定义API方法和路径的地方:
echo.labstack

/src/infrastructure/router.go

package infrastructure

import (
    controllers "echoSample/src/interfaces/api"
    "net/http"

    "github.com/labstack/echo"
)

func Init() {
    // Echo instance
    e := echo.New()
    userController := controllers.NewUserController(NewSqlHandler())

    e.GET("/users", func(c echo.Context) error {
        users := userController.GetUser() 
        c.Bind(&users) 
        return c.JSON(http.StatusOK, users)
    })

    e.POST("/users", func(c echo.Context) error {
        userController.Create(c)
        return c.String(http.StatusOK, "created")
    })

    e.DELETE("/users/:id", func(c echo.Context) error {
        id := c.Param("id")
        userController.Delete(id)
        return c.String(http.StatusOK, "deleted")
    })

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}

接口

在控制器和演示者层中,我们需要意识到依赖项。

从接口层调用域和用户酶层不是问题,但是我们不能直接调用基础结构层。相反,我们定义了一个接口(基础结构层中定义的SQLHandler接口)。

/src/interfaces/api/user_controller.go

package controllers

import (
    "echoSample/src/domain"
    "echoSample/src/interfaces/database"
    "echoSample/src/usecase"

    "github.com/labstack/echo"
)

type UserController struct {
    Interactor usecase.UserInteractor
}

func NewUserController(sqlHandler database.SqlHandler) *UserController {
    return &UserController{
        Interactor: usecase.UserInteractor{
            UserRepository: &database.UserRepository{
                SqlHandler: sqlHandler,
            },
        },
    }
}

func (controller *UserController) Create(c echo.Context) {
    u := domain.User{}
    c.Bind(&u)
    controller.Interactor.Add(u)
    createdUsers := controller.Interactor.GetInfo()
    c.JSON(201, createdUsers)
    return
}

func (controller *UserController) GetUser() []domain.User {
    res := controller.Interactor.GetInfo()
    return res
}

func (controller *UserController) Delete(id string) {
    controller.Interactor.Delete(id)
}

控制器调用用户酶和域层,这不是问题。

src/interfaces/api/context.go

package controllers

type Context interface {
    Param(string) string
    Bind(interface{}) error
    Status(int)
    JSON(int, interface{})
}

关于数据库:

src/interfaces/database/user_repository.go

package database

import (
    "echoSample/src/domain"
)

type UserRepository struct {
    SqlHandler
}

func (db *UserRepository) Store(u domain.User) {
    db.Create(&u)
}

func (db *UserRepository) Select() []domain.User {
    user := []domain.User{}
    db.FindAll(&user)
    return user
}
func (db *UserRepository) Delete(id string) {
    user := []domain.User{}
    db.DeleteById(&user, id)
}

在存储库中,sqlhandler被调用,但我们没有直接调用基础结构层的sqlhandler,而是通过在同一层中定义的sqlhandler接口称为。
这被称为依赖性反转原理。

src/interfaces/db/sql_handler.go

软件包数据库

type SqlHandler interface {
    Create(object interface{})
    FindAll(object interface{})
    DeleteById(object interface{}, id string)
}

这样,我们可以调用sql_handler的功能。

用户酶

最后,我们有USECase层。

src/usecase/user_interactor.go

package usecase

import "echoSample/src/domain"

type UserInteractor struct {
    UserRepository UserRepository
}

func (interactor *UserInteractor) Add(u domain.User) {
    interactor.UserRepository.Store(u)
}

func (interactor *UserInteractor) GetInfo() []domain.User {
    return interactor.UserRepository.Select()
}

func (interactor *UserInteractor) Delete(id string) {
    interactor.UserRepository.Delete(id)
}

在这里,像以前一样,我们需要应用依赖关系反转原则,因此我们定义user_repository.go。

src/usecase/user_repository.go

package usecase

import (
    "echoSample/src/domain"
)

type UserRepository interface {
    Store(domain.User)
    Select() []domain.User
    Delete(id string)
}

这样,我们的实施已完成。
现在,通过使用docker-compose.yml运行mySQL并启动服务器,API应该可以工作。

version: "3.6"
services:
  db:
    image: mysql:5.7
    container_name: go_sample
    volumes:
      # mysql configuration
      - ./mysql/conf:/etc/mysql/conf.d
      - ./mysql/data:/var/lib/mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: go_sample
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: root
      TZ: "Asia/Tokyo"

src/server.go

package main

import (
    "echoSample/src/domain"
    "echoSample/src/infrastructure"

    "github.com/labstack/echo/v4"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var (
    db  *gorm.DB
    err error
    dsn = "root:password@tcp(127.0.0.1:3306)/go_sample?charset=utf8mb4&parseTime=True&loc=Local"
)

func main() {
    dbinit()
    infrastructure.Init()
    e := echo.New()
    e.Logger.Fatal(e.Start(":1323"))
}

func dbinit() {
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
    }
    db.Migrator().CreateTable(domain.User{})
}

运行mysql

docker -compose -d

运行服务器

Go Run Serve.go

致电API

POST: /users

curl --location --request POST 'localhost:1323/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name":"J.Y Park"
}'
curl --location --request POST 'localhost:1323/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name":"Eron Mask"
}'

GET: /users

curl --location --request GET localhost:1323/users'

它有效!

Conclusion

不仅阅读有关干净体系结构的文章,而且还可以实际创建一个简单的API并测试其功能,您的理解将加深。
但是,老实说,在从事CRUD应用程序量表的项目时,干净体系结构的优势可能不会完全理解。
干净的体系结构不仅提高了代码的可读性和生产率,而且具有抵抗变化的特征。因此,通过在我这次创建的应用程序中添加各种功能来体验其好处是很高兴的!