In diesem Artikel heißt es "Saubere Architektur". Verwenden Sie "MySQL" für DB, "Echo" für Framework und "GORM" für ORMapper. Wir werden eine API erstellen, die eine CR (U) D-Funktion hat.
API mit Funktionen zum Erstellen, Lesen (Aktualisieren) und Löschen Nur das Update ist nicht implementiert, bitte fügen Sie es selbst hinzu!
Diejenigen, die nach Abschluss der Go-Umgebungskonstruktion eine einfache API erstellen möchten
Technologie | Art |
---|---|
DB | Mysql |
ORM | GORM |
Rahmen | Echo |
Wenn es um saubere Architektur geht, ist die folgende Abbildung sehr berühmt.
Der Zweck von Clean Architecture ist ** Interessentrennung ** Um dies zu erreichen, müssen Sie sich der Abhängigkeit jeder Schicht bewusst sein.
Die Trennung von Interesse verbessert die Lesbarkeit des Codes und macht ihn zu einem änderungsresistenten Design. Informationen zu den Vorteilen dieses Bereichs und Einzelheiten zu Clean Architecture finden Sie in Referenzartikel.
In der obigen Abbildung zeigt der Pfeil von außen nach innen, was die ** abhängige Ausrichtung ** ist. Es ist möglich, von außen nach innen zu hängen, aber nicht von innen nach außen.
Mit anderen Worten, ** innen deklarierte Dinge können von außen aufgerufen werden **, aber außen deklarierte Dinge ** können nicht von innen aufgerufen werden **.
In diesem Artikel werde ich den Code ** vorstellen und dabei auf die Richtung der Abhängigkeit achten.
Die Endpunkte jeder Funktion sind wie folgt
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
Diesmal die Ebene von Clean Arhictecture und die Verzeichnisstruktur Sie können wie folgt vergleichen.
Verzeichnisname | Schicht |
---|---|
domain | Entities |
usecase | Use Cases |
interface | Controllers Presenters |
infrastructure | External Interfaces |
domain
Definieren Sie in der Domänenschicht die Entität.
Da es sich in der Mitte befindet, kann es von jeder Ebene aus aufgerufen werden.
src/domain/user.go
package domain
type User struct {
ID int `json:"id" gorm:"primary_key"`
Name string `json:"name"`
}
Erstellen Sie dieses Mal einen Benutzer mit einer ID und einem Namen in der Spalte und legen Sie die ID als Primärschlüssel fest.
Über json:" id "gorm:" primary_key "
json:" id "
hat eine json-Zuordnung
gorm:" primary_key "
markiert das Modell mit gorm.
Zusätzlich zu primary_key
können Sie natürlich das Gleiche tun wie beim Definieren einer Tabelle mit SQL, z. B. not null`` unique`` default
.
Referenz GORM Declearing Model
infrastructure
Äußerste ** Infrastruktur **
Ich werde den Teil schreiben, in dem sich die Anwendung auf die Außenseite bezieht. In diesem Beispiel werden hier ** Verbindung mit DB ** und ** Router ** definiert.
Da es sich um die äußerste Ebene handelt, können Sie sie aufrufen, ohne eine Ebene zu kennen.
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)
}
Das "offizielle Dokument" war hilfreich für die DB-Verbindung.
Als nächstes kommt das Routing. Dieses Mal verwende ich das Webframework Echo. Ich habe auch auf das "offizielle Dokument" verwiesen. Es definiert die API-Methode und den Pfad. 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
Ebenen von Controllern Präsentatoren.
Von hier an müssen Sie sich der Abhängigkeiten bewusst sein.
Es gibt kein Problem beim Aufrufen der ** Schnittstellenschicht ** zur ** Domänenschicht ** und ** Verwendungsschicht **.
Sie können die ** Infrastrukturschicht ** nicht aufrufen. Definieren Sie daher die Schnittstelle, anstatt sie direkt aufzurufen. (Es ist kompliziert, aber es ist die Schnittstelle von sqlHandler, die in der Inrastrucure-Ebene definiert ist.)
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)
}
Es gibt kein Problem, da der Controller von der Usecase-Schicht und der Domänenschicht aus aufruft.
src/interfaces/api/context.go
package controllers
type Context interface {
Param(string) string
Bind(interface{}) error
Status(int)
JSON(int, interface{})
}
datenbankbezogen
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)
}
Im Repository rufen wir sqlHandler auf, aber anstatt Dinge in der Infrastrukturschicht direkt aufzurufen. Es wird über die ** sqlhandler-Schnittstelle ** aufgerufen, die in derselben Hierarchie definiert ist.
Dies wird als ** Prinzip der Abhängigkeitsumkehr ** bezeichnet.
src/interfaces/db/sql_handler.go
package database
type SqlHandler interface {
Create(object interface{})
FindAll(object interface{})
DeleteById(object interface{}, id string)
}
Jetzt können Sie den Prozess von sql_handler aufrufen.
usecase Last but not least die Usecase-Schicht.
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)
}
Auch hier müssen wir das Prinzip der Abhängigkeitsumkehr wie zuvor anwenden.
Definieren Sie also 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)
}
Damit ist die Implementierung abgeschlossen.
Starten Sie danach mysql mit docker-compose.yml, starten Sie den Server und es sollte funktionieren.
docker-compose.yml
version: "3.6"
services:
db:
image: mysql:5.7
container_name: go_sample
volumes:
#MySQL-Einstellungen
- ./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{})
}
Starten Sie MySQL mit dem folgenden Befehl
docker-compose up -d
Serverstart
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
bekommen und überprüfen
{"id":2,"name":"Eron Mask"}]
Wenn Sie nicht nur den Artikel über Clean Architecture lesen, sondern auch eine einfache API erstellen und den Vorgang überprüfen, wird Ihr Verständnis vertieft.
Mein ehrlicher Eindruck war jedoch, dass ich den Reiz von Clean Architecture im Maßstab einer CRUD-App nicht erleben konnte.
Saubere Architektur verbessert nicht nur die Lesbarkeit und Produktivität des Codes, sondern hat auch die Eigenschaft, ** resistent gegen Änderungen ** zu sein. Daher möchte ich dem Produkt, das ich dieses Mal hergestellt habe, verschiedene Dinge hinzufügen und die Güte erkennen. ...!
Die am einfachsten zu verstehende saubere Architektur der Welt Saubere Architektur (Übersetzung) Versuchen Sie, einen API-Server mit sauberer Architektur zu erstellen Golang - Erstellen einer API für saubere Architektur mit Echo und GORM