Erstellen Sie eine CRUD-App mit Go with Mysql, GORM, Echo, Clean Architecture

Einführung

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.

Was zu machen

API mit Funktionen zum Erstellen, Lesen (Aktualisieren) und Löschen Nur das Update ist nicht implementiert, bitte fügen Sie es selbst hinzu!

Zielgruppe

Diejenigen, die nach Abschluss der Go-Umgebungskonstruktion eine einfache API erstellen möchten

Technologie verwendet

Technologie Art
DB Mysql
ORM GORM
Rahmen Echo

Inhaltsverzeichnis

Was ist saubere Architektur?

Wenn es um saubere Architektur geht, ist die folgende Abbildung sehr berühmt.

clean.jpeg

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.

In Bezug auf jede Funktion

Die Endpunkte jeder Funktion sind wie folgt

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

Verzeichnisaufbau

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. entitys.png

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. framework.png

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.

gorm.io

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.

framework-controller.png

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.

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

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

Funktionsprüfung

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"}]

Zusammenfassung

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. ...!

Referenzartikel

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

Recommended Posts

Erstellen Sie eine CRUD-App mit Go with Mysql, GORM, Echo, Clean Architecture
Beispielcode für den Sprachserver mit Clean Architecture-ähnlicher Konfiguration (mit Echo, Wire)
GO gorm DB-Erstellung INSERT JSONUnmarshal Mysql