Créez des applications CRUD avec Go avec Mysql, GORM, Echo, Clean Architecture

introduction

Dans cet article, il s'agit de «Clean Architecture». Utilisez Mysql pour DB, ʻEcho pour le framework et GORM pour ORMapper. Nous allons créer une API qui a une fonction CR (U) D`.

Quoi faire

API avec fonctions Créer, Lire, (Mettre à jour), Supprimer Seule la mise à jour n'est pas encore implémentée, veuillez donc l'ajouter vous-même!

Public cible

Ceux qui veulent créer une API simple après avoir terminé la construction de l'environnement Go

Technologie utilisée

La technologie type
DB Mysql
ORM GORM
Cadre Echo

table des matières

Qu'est-ce que l'architecture propre?

En ce qui concerne l'architecture propre, la figure suivante est très connue.

clean.jpeg

Le but de l'architecture propre est ** Séparation des intérêts ** Ce dont vous devez être conscient pour y parvenir, c'est la ** dépendance de chaque couche **.

La séparation des intérêts améliore la lisibilité du code et en fait une conception résistante au changement. Veuillez vous référer à Article de référence pour les avantages de ce domaine et les détails de Clean Architecture.

Dans la figure ci-dessus, la flèche pointe de l'extérieur vers l'intérieur du cercle, qui correspond à ** l'orientation dépendante **. Il est possible de dépendre de l'extérieur vers l'intérieur, mais pas de l'intérieur vers l'extérieur.

En d'autres termes, ** les choses déclarées à l'intérieur peuvent être appelées de l'extérieur **, mais les choses déclarées à l'extérieur ** ne peuvent pas être appelées de l'intérieur **.

Dans cet article, je présenterai le code **, en faisant attention au sens de la dépendance.

Concernant chaque fonction

Les extrémités de chaque fonction sont les suivantes

POST: /create GET: /users DELETE: /delete/:id

Structure du répertoire

echoSample
│
│── src
│    ├── domain 
│    │    └── user.go
│    ├── infrastructure
│    │    ├── router.go
│    │    └── sqlHandler.go
│    ├── interfaces
│    │    ├── api
│    │        ├── context.go
│    │        └── user_controller.go
│    │    └── database
│    │        ├── sql_handler.go
│    │        └── user_repository.go
│    ├── usecase
│    │    ├── user_interactor.go
│    │    └── user_repository.go
│    ├── server.go
│        
└── docker-compose.yml 

La couche de Clean Arhictecture et la structure des répertoires cette fois Vous pouvez comparer comme suit.

Nom du répertoire couche
domain Entities
usecase Use Cases
interface Controllers Presenters
infrastructure External Interfaces

domain Dans la couche de domaine, définissez l'entité. Puisqu'il est au centre, il peut être appelé depuis n'importe quel calque. entitys.png

src/domain/user.go


package domain

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

Cette fois, créez ʻUser` avec l'ID et le nom dans la colonne et définissez id comme clé primaire.

À propos de json:" id "gorm:" primary_key "

json:" id " a un mappage json gorm:" primary_key " marque le modèle avec gorm. Bien sûr, en plus de primary_key, vous pouvez faire la même chose que définir une table avec sql, comme not null ʻunique`` default`.

référence GORM Declearing Model

infrastructure

Infrastructures ultrapériphériques ** J'écrirai la partie où l'application est liée à l'extérieur. Dans cet exemple, ** Connexion avec DB ** et ** Router ** sont définis ici. framework.png

Puisqu'il s'agit de la couche la plus externe, vous pouvez l'appeler sans avoir connaissance d'aucune couche.

src/infrastucture/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)
}

Le «document officiel» était utile pour la connexion à la base de données.

gorm.io

Vient ensuite le routage. Cette fois, j'utilise le framework Web Echo. J'ai également évoqué le «document officiel». Il définit la méthode et le chemin de l'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("/create", func(c echo.Context) error {
		userController.Create(c)
		return c.String(http.StatusOK, "created")
	})

	e.DELETE("/delete/: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"))
}


interfaces

Couches de présentateurs de contrôleurs.

À partir de là, vous devez être ** conscient des dépendances **.

framework-controller.png

Il n'y a aucun problème avec l'appel de ** interface layer ** à ** domain layer ** et ** usecase layer **.

Vous ne pouvez pas appeler la ** couche d'infrastructure **, définissez donc l'interface au lieu de l'appeler directement. (C'est compliqué, mais c'est l'interface de sqlHandler définie dans la couche inrastrucure)

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

Il n'y a pas de problème car le contrôleur appelle à partir de la couche de cas d'utilisation et de la couche de domaine.

src/interfaces/api/context.go


package controllers

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

lié à la base de données

src/interfaces/database/user_repository.go


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

Dans le référentiel, nous appelons sqlHandler, mais au lieu d'appeler directement les éléments de la couche infrastructure. Il est appelé via l'interface ** sqlhandler ** définie dans la même hiérarchie.

C'est ce qu'on appelle le ** principe de l'inversion des dépendances **.

src/interfaces/db/sql_handler.go


package database

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

Vous pouvez maintenant appeler le processus de sql_handler.

usecase Dernier point mais non le moindre, la couche de cas d'utilisation.

controller-usecase.png

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

Encore une fois, nous devons appliquer le principe du renversement de la dépendance comme auparavant.

Définissez donc 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)
}

Ceci termine la mise en œuvre.

Après cela, démarrez mysql avec docker-compose.yml, démarrez le serveur et cela devrait fonctionner.

docker-compose.yml


version: "3.6"
services:
  db:
    image: mysql:5.7
    container_name: go_sample
    volumes:
      #paramètres mysql
      - ./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{})
}


Démarrez mysql avec la commande suivante

docker-compose up -d

Démarrage du serveur

go run server.go

Contrôle de fonctionnement

POST: /create

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

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


docker-compose

created%                                                                                                                                                                                                               

GET: /users

curl --location --request GET 'localhost:1323/users'
[{"id":1,"name":"J.Y Park"},{"id":2,"name":"Eron Mask"}]

DELETE: /delete/:id

curl --location --request DELETE 'localhost:1323/delete/1'
deleted

obtenir et vérifier

{"id":2,"name":"Eron Mask"}]

Sommaire

Non seulement la lecture de l'article sur Clean Architecture, mais la création d'une API simple et la vérification de l'opération approfondiront votre compréhension.

Cependant, mon impression honnête était que je ne pouvais pas ressentir l'attrait de l'architecture propre à l'échelle d'une application CRUD.

L'architecture propre améliore non seulement la lisibilité et la productivité du code, mais a également la propriété d'être ** résistante au changement **, je voudrais donc ajouter diverses choses au produit que j'ai fait cette fois et en réaliser la bonté. ...!

Article de référence

L'architecture propre la plus facile à comprendre au monde Architecture propre (traduction) Essayez de créer un serveur API avec une architecture propre Golang --Construction d'une API d'architecture propre avec Echo et GORM

Recommended Posts

Créez des applications CRUD avec Go avec Mysql, GORM, Echo, Clean Architecture
Exemple de code de serveur de langage Go avec une configuration de type Clean Architecture (utilisant echo, wire)
Création d'une base de données GO gorm INSÉRER JSONUnmarshal Mysql