[MySQL, Go] Пишем CRUD-приложение на Go с помощью Mysql, GORM, Echo, Clean Architecture (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Начнем сначала
В этой статье будет сказ о том, как на Clean Architecture написать API с функциями CR(U)D, где в качестве БД взят Mysql, фреймворк – Echo, ORMapper – GORM.
Что делаем
API с функциями Create, Read, (Update), Delete. Обновление на самом деле реализовать особо не удалось, милости прошу попробовать самостоятельно.
Целевая аудитория
Те разработчики, которые хотят создать простой API после освоения Go.
Основное cодержание
Что такое Clean Architecture, и с чем его едят
Это мы можем подробно рассмотреть на следующей картинке:
Цель Clean Architecture – это разделение “сфер”. Чтобы удачно добиться этого разделения, нужно всегда держать в голове зависимости каждого слоя на картинке с остальными. Разделение на слои улучшает читаемость кода и делает его устойчивым к изменениям.
На рисунке выше стрелка указывает снаружи внутрь круга — это направление зависимости. Важно: зависимости “направлены” извне внутрь, не наоборот.
Другими словами, вы можете снаружи вызывать вещи, объявленные внутри, но вы не можете вызывать вещи, объявленные снаружи, находясь внутри.
Код данного приложения написан с учетом зависимостей, как и завещает Clean Architecture.
О функциях
Endpoint’ы каждой функции следующие:
POST: /users
GET: /users
DELETE: /users/:id
Структура директорий
Взаимное отображение слоев архитектуры и директорий выглядит так:
- domain — Entities
- usecase — Use Cases
- interface — Controllers Presenters
- infrasctructure — External Interfaces
domain
Директории domain соответствует слой Entity. Так как он находится в самом ядре, вызываться может с любого слоя.
src/domain/user.go
package domain
type User struct {
ID int `json:"id" gorm:"primary_key"`
Name string `json:"name"`
}
Создаем struct User с id и именем и устанавливаем идентификатор в качестве первичного ключа.
Немного о json:«id» gorm:«primary_key»:
В json:«id» json отвечает за маппинг. В gorm:«primary_key» идет пометка на модели с помощью gorm.
Конечно, помимо primary_key вы можете использовать not null, unique, default и т.д.,
Полезная ссылка – объявление моделей на GORM
infrastructure
Самый крайний, внешний слой. Здесь описывается часть, в которой приложение связано с “внешним миром”. В нашем случае, в этом слое объявляется связь с БД и Router.
Поскольку это самый внешний слой, вы можете вызывать его, не имея данных о любом другом слое.
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)
}
По поводу баз данных я оставлю ссылки на документацию: gorm.io
Далее идет маршрутизация.
В этом приложении я использую веб-фреймворк Echo. Фреймворк определяет Method и Path API
Ссылка на документацию: https://echo.labstack.com/
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("/users", func(c echo.Context) error {
userController.Create(c)
return c.String(http.StatusOK, "created")
})
e.DELETE("/users/: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
Слой Controllers Presenters.
Вот здесь уже нужно вспомнить о зависимостях.
Нет проблем с вызовом со слоев domain и usecase, но нельзя вызвать слой infrastructure напрямую, поэтому объявим interface. (Получилось немного запутанно, но речь идет про интерфейс sqlHandler, определенный на слое infrastrucure)
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)
}
В controller вызываем со слоёв domain и usecase, поэтому проблем нет.
src/interfaces/api/context.go
package controllers
type Context interface {
Param(string) string
Bind(interface{}) error
Status(int)
JSON(int, interface{})
}
Связь с БД
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)
}
В repository вызывается sqlHandler, но он вызывается не напрямую со слоя infrastructure, а с помощью объявленного там же interface.
Это называется принципом инверсии зависимостей.
src/interfaces/db/sql_handler.go
package database
type SqlHandler interface {
Create(object interface{})
FindAll(object interface{})
DeleteById(object interface{}, id string)
}
Теперь вы можете вызывать процесс sql_handler.
usecase
Последний оставшийся слой, usecase.
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)
}
Опять же, нам нужно применить принцип инверсии зависимости, как и раньше. Поэтому определяем 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)
}
На этом реализация завершена.
После этого запустите mysql с docker-compose.yml, запустите сервер, и все должно работать.
docker-compose.yml
version: "3.6"
services:
db:
image: mysql:5.7
container_name: go_sample
volumes:
# настройки 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"
===========
Источник:
habr.com
===========
===========
Автор оригинала: Ichino
===========Похожие новости:
- [JavaScript, Веб-аналитика, Интернет-маркетинг, Повышение конверсии, Поисковая оптимизация] Используем Google Tag Manager Server-Side вместо Zapier
- [Законодательство в IT, IT-компании] Роскомнадзор опять обнаружил, что Google не удаляет из поисковой выдачи запрещенную информацию
- [Машинное обучение] Как получить новый сертификат инженера по машинному обучению от Google Cloud
- Выпуск рабочего стола Regolith 1.5
- [JavaScript, Google App Engine] Google Apps Script: переносим расписание из таблицы в календарь
- [Программирование, Go] JSON с опциональными полями в Go (перевод)
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.48.0: упрощение создания ссылок и псевдонимы поиска (перевод)
- [Open source, Google Chrome, Разработка под Linux] Ubuntu Web Remix — альтернатива Chrome OS c браузером Firefox вместо Google Chrome
- [Google App Engine, Облачные вычисления, Google Cloud Platform] Обзор на курс специализации от Coursera — Cloud Architecture with Google Cloud
- [Google Chrome, Браузеры] Google обещает значительный прирост производительности в Chrome 87
Теги для поиска: #_mysql, #_go, #_go, #_golang, #_crud, #_clean_architecture, #_echo, #_mysql, #_sloistaja_arhitektura (слоистая архитектура), #_mysql, #_go
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:29
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Начнем сначала В этой статье будет сказ о том, как на Clean Architecture написать API с функциями CR(U)D, где в качестве БД взят Mysql, фреймворк – Echo, ORMapper – GORM. Что делаем API с функциями Create, Read, (Update), Delete. Обновление на самом деле реализовать особо не удалось, милости прошу попробовать самостоятельно. Целевая аудитория Те разработчики, которые хотят создать простой API после освоения Go. Основное cодержание Что такое Clean Architecture, и с чем его едят Это мы можем подробно рассмотреть на следующей картинке: Цель Clean Architecture – это разделение “сфер”. Чтобы удачно добиться этого разделения, нужно всегда держать в голове зависимости каждого слоя на картинке с остальными. Разделение на слои улучшает читаемость кода и делает его устойчивым к изменениям. На рисунке выше стрелка указывает снаружи внутрь круга — это направление зависимости. Важно: зависимости “направлены” извне внутрь, не наоборот. Другими словами, вы можете снаружи вызывать вещи, объявленные внутри, но вы не можете вызывать вещи, объявленные снаружи, находясь внутри. Код данного приложения написан с учетом зависимостей, как и завещает Clean Architecture. О функциях Endpoint’ы каждой функции следующие: POST: /users GET: /users DELETE: /users/:id Структура директорий Взаимное отображение слоев архитектуры и директорий выглядит так:
domain Директории domain соответствует слой Entity. Так как он находится в самом ядре, вызываться может с любого слоя. src/domain/user.go package domain
type User struct { ID int `json:"id" gorm:"primary_key"` Name string `json:"name"` } Создаем struct User с id и именем и устанавливаем идентификатор в качестве первичного ключа. Немного о json:«id» gorm:«primary_key»: В json:«id» json отвечает за маппинг. В gorm:«primary_key» идет пометка на модели с помощью gorm. Конечно, помимо primary_key вы можете использовать not null, unique, default и т.д., Полезная ссылка – объявление моделей на GORM infrastructure Самый крайний, внешний слой. Здесь описывается часть, в которой приложение связано с “внешним миром”. В нашем случае, в этом слое объявляется связь с БД и Router. Поскольку это самый внешний слой, вы можете вызывать его, не имея данных о любом другом слое. 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) } По поводу баз данных я оставлю ссылки на документацию: gorm.io Далее идет маршрутизация. В этом приложении я использую веб-фреймворк Echo. Фреймворк определяет Method и Path API Ссылка на документацию: https://echo.labstack.com/ 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("/users", func(c echo.Context) error { userController.Create(c) return c.String(http.StatusOK, "created") }) e.DELETE("/users/: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 Слой Controllers Presenters. Вот здесь уже нужно вспомнить о зависимостях. Нет проблем с вызовом со слоев domain и usecase, но нельзя вызвать слой infrastructure напрямую, поэтому объявим interface. (Получилось немного запутанно, но речь идет про интерфейс sqlHandler, определенный на слое infrastrucure) 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) } В controller вызываем со слоёв domain и usecase, поэтому проблем нет. src/interfaces/api/context.go package controllers
type Context interface { Param(string) string Bind(interface{}) error Status(int) JSON(int, interface{}) } Связь с БД 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) } В repository вызывается sqlHandler, но он вызывается не напрямую со слоя infrastructure, а с помощью объявленного там же interface. Это называется принципом инверсии зависимостей. src/interfaces/db/sql_handler.go package database
type SqlHandler interface { Create(object interface{}) FindAll(object interface{}) DeleteById(object interface{}, id string) } Теперь вы можете вызывать процесс sql_handler. usecase Последний оставшийся слой, usecase. 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) } Опять же, нам нужно применить принцип инверсии зависимости, как и раньше. Поэтому определяем 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) } На этом реализация завершена. После этого запустите mysql с docker-compose.yml, запустите сервер, и все должно работать. docker-compose.yml
version: "3.6" services: db: image: mysql:5.7 container_name: go_sample volumes: # настройки 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" =========== Источник: habr.com =========== =========== Автор оригинала: Ichino ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:29
Часовой пояс: UTC + 5