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`.
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!
Ceux qui veulent créer une API simple après avoir terminé la construction de l'environnement Go
La technologie | type |
---|---|
DB | Mysql |
ORM | GORM |
Cadre | Echo |
En ce qui concerne l'architecture propre, la figure suivante est très connue.
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.
Les extrémités de chaque fonction sont les suivantes
POST: /create
GET: /users
DELETE: /delete/:id
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.
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.
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.
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 **.
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.
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
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"}]
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é. ...!
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