In this article, it is Clean Architecture
.
Use Mysql
for DB, ʻEcho for framework, and
GORMfor ORMapper. We will create an API with
CR (U) D function`.
API with Create, Read, (Update), Delete functions Only Update is not implemented yet, so please add it yourself!
Those who want to create a simple API after completing the Go environment construction
Technology | type |
---|---|
DB | Mysql |
ORM | GORM |
Framework | Echo |
When it comes to Clean Architecture, the following figure is very famous.
The purpose of Clean Architecture is ** separation of concerns ** What you need to be aware of to achieve this is ** dependency of each layer **.
Separation of concerns improves code readability and makes the design resistant to change. Please refer to Reference article for the advantages of this area and details of Clean Architecture.
In the figure above, the arrows are pointing from the outside to the inside of the circle, which is the ** dependent orientation **. It is possible to depend from the outside to the inside, but not from the inside to the outside.
In other words, ** things declared inside can be called from the outside **, but things declared outside ** cannot be called from the inside **.
In this article, I will introduce the code **, paying attention to the direction of dependence.
The endpoints of each function are as follows
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
Clean Arhictecture layer and directory structure this time You can compare as follows.
Directory name | layer |
---|---|
domain | Entities |
usecase | Use Cases |
interface | Controllers Presenters |
infrastructure | External Interfaces |
domain
Entity is defined in the domain layer.
Since it is in the center, it can be called from any layer.
src/domain/user.go
package domain
type User struct {
ID int `json:"id" gorm:"primary_key"`
Name string `json:"name"`
}
This time, create ʻUser` with ID and Name in the column and set id as the primary key.
About json:" id "gorm:" primary_key "
json:" id "
has json mapping
gorm:" primary_key "
tags the model with gorm.
Of course, in addition to primary_key
, you can do the same as defining a table with sql, such as not null
unique`` default
.
reference GORM Declearing Model
infrastructure
Outermost ** Infrastructure **
I will write the part where the application is related to the outside. In this example, ** Connection with DB ** and ** Router ** are defined here.
Since it is the outermost layer, you can call it without being aware of any layer.
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)
}
The official document
was helpful around the DB connection.
Next is routing.
This time I'm using the web framework Echo. I also referred to the official document
. It defines API Method and path.
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
Layers of Controllers Presenters.
From here on, you need to be ** aware of dependencies **.
There is no problem with calling ** interface layer ** to ** domain layer ** and ** usecase layer **.
You cannot call the ** infrastructure layer **, so define the interface instead of calling it directly. (It's complicated, but it's the interface of sqlHandler defined in the inrastrucure layer)
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)
}
There is no problem because the controller calls from the usecase layer and domain layer.
src/interfaces/api/context.go
package controllers
type Context interface {
Param(string) string
Bind(interface{}) error
Status(int)
JSON(int, interface{})
}
database related
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)
}
In the repository, we call sqlHandler, but instead of calling things in the infrastructure layer directly It is called through the ** sqlhandler interface ** defined in the same hierarchy.
This is called the ** Dependency Inversion Principle **.
src/interfaces/db/sql_handler.go
package database
type SqlHandler interface {
Create(object interface{})
FindAll(object interface{})
DeleteById(object interface{}, id string)
}
Now you can call the process of sql_handler.
usecase Last but not least, the usecase layer.
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)
}
Again, we need to apply the Dependency Inversion Principle as before.
So define 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)
}
This completes the implementation.
After that, start mysql with docker-compose.yml, start the server, and it should work.
docker-compose.yml
version: "3.6"
services:
db:
image: mysql:5.7
container_name: go_sample
volumes:
#mysql settings
- ./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{})
}
Start mysql with the following command
docker-compose up -d
Server startup
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
get and check
{"id":2,"name":"Eron Mask"}]
Not only reading the article about Clean Architecture, but actually making a simple API and checking the operation will deepen your understanding.
However, my honest impression was that I couldn't experience the appeal of Clean Architecture on the scale of a CRUD app.
Clean Architecture not only improves the readability and productivity of the code, but also has the property of being ** resistant to change **, so I would like to add various things to the product I made this time and realize the goodness. ...!
The most easy-to-understand Clean Architecture in the world Clean Architecture (Translation) Try building API Server with Clean Architecture Building Clean Architecture API with Golang --Echo and GORM
Recommended Posts