Введение

Построение API-интерфейсов, которыми выполняются CRUD-операции  —  создание, чтение, обновление и удаление,  —  является общим требованием для многих веб-приложений. После продолжительных поисков быстрого и надежного фреймворка для этой задачи обнаружилось много Open Source проектов, которыми ускоряется процесс разработки. Напишем REST API с базой данных MySQL на своенравном GoFr и платформе для веб-приложений Gin, сопоставим функционал этих двух фреймворков.

Настройка

  1. Go версии 1.21 и новее.
  2. Установленные MySQL или Docker.

Устанавливаем образ Docker MySQL:

docker run --name gofr-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=test -p 2001:3306 -d mysql:8.0.30

Создание REST API с GoFr

Установка

Получаем gofr:

go get gofr.dev

Пример

Подключаемся к базе данных mysql, добавляя в файл configs/.env такие конфигурации:

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=password
DB_NAME=test
DB_PORT=2001
DB_DIALECT=mysql

С помощью функционала GoFr регистрируем обработчики из данной сущности:

package main

import (
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/migration"
)

const createTable = `CREATE TABLE IF NOT EXISTS user (
id int auto_increment primary key,
name varchar(50) not null,
age int not null
);`

func createTableUser() migration.Migrate {
return migration.Migrate{
UP: func(d migration.Datasource) error {
_, err := d.SQL.Exec(createTable)
if err != nil {
return err
}

return nil
},
}
}

type user struct {
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
// Создаем новое приложение
a := gofr.New()

// Добавляем миграции для запуска
a.Migrate(map[int64]migration.Migrate{1708322061: createTableUser()})

// С «AddRESTHandlers» для сущности создаются дескрипторы CRUD
err := a.AddRESTHandlers(&user{})
if err != nil {
return
}

// Запускаем приложение
a.Run()
}

С легко интегрируемым в настройку приложений функционалом GoFr повышается продуктивность разработчиков, обеспечивается создание готовых к промышленной эксплуатации приложений.

Среди функционала GoFr выделяется следующее.

Встроенная поддержка управления миграциями баз данных, благодаря которой разработчики эффективно определяют и осуществляют изменения схемы БД.

Соблюдение стандартов REST по умолчанию, упрощаемое автоматическим генерированием обработчиков CRUD для сущностей, более легкой разработкой API, обеспечением согласованности при проектировании конечных точек.

Поддержка трассировки и метрик по умолчанию, благодаря которой разработчики эффективно отслеживают производительность приложений и диагностируют проблемы.

Комплексная проверка работоспособности, легко отслеживаемой вместе со статусом приложения.

AddRESTHandlers. При добавлении обработчика REST из структуры GoFr, чтобы выполнять CRUD-операции в экземплярах этой структуры, фреймворком автоматически создаются конечные точки HTTP. Так упрощается разработка API, ведь вручную определять конечные точки и реализовывать функциональность CRUD для каждой сущности не нужно.

Создание REST API с Gin

Установка

Получаем gin:

go get github.com/gin-gonic/gin

Пример

gin не поддерживается создание таблиц в базе данных, поэтому воспользуемся БД, созданной на GoFr в предыдущем примере:

package main

import (
"database/sql"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"log"
"net/http"
)

type User struct {
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}

type handler struct {
db *sql.DB
}

func main() {
db, err := sql.Open("mysql", "root:password@tcp(localhost:2001)/test")
if err != nil {
log.Fatal(err)
}
defer db.Close()

r := gin.Default()
h := handler{db}

r.GET("/user", h.GetUsers)
r.POST("/user", h.CreateUser)
r.PUT("/user/:id", h.UpdateUser)
r.DELETE("/user/:id", h.DeleteUser)

r.Run(":8080")
}

// Получаем всех пользователей
func (h *handler) GetUsers(c *gin.Context) {
rows, err := h.db.Query("SELECT id, name, age FROM user")
if err != nil {
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
return
}
defer rows.Close()

users := make([]User, 0)
for rows.Next() {
var user User
err := rows.Scan(&user.Id, &user.Name, &user.Age)
if err != nil {
log.Println(err)
continue
}
users = append(users, user)
}

c.JSON(http.StatusOK, users)
}

// Создаем нового пользователя
func (h *handler) CreateUser(c *gin.Context) {
var user User

if err := c.BindJSON(&user); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

result, err := h.db.Exec("INSERT INTO user (name, age) VALUES (?, ?)", user.Name, user.Age)
if err != nil {
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
return
}

id, _ := result.LastInsertId()
user.Id = int(id)

c.JSON(http.StatusCreated, user)
}

// Обновляем имеющегося пользователя
func (h *handler) UpdateUser(c *gin.Context) {
id := c.Param("id")

var user User
if err := c.BindJSON(&user); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

result, err := h.db.Exec("UPDATE users SET name = ?, age = ? WHERE id = ?", user.Name, user.Age, id)
if err != nil {
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user"})
return
}

rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
return
}

c.JSON(http.StatusOK, user)
}

// Удаляем пользователя
func (h *handler) DeleteUser(c *gin.Context) {
id := c.Param("id")

result, err := h.db.Exec("DELETE FROM user WHERE id = ?", id)
if err != nil {
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user"})
return
}

rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
return
}

c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
}

Сравнение

Несмотря на легкость и гибкость Gin по сравнению с GoFr требуется дополнительная конфигурация и ручная настройка некоторого функционала.

Основные различия:

Отсутствие управления схемой БД. В отличие от GoFr, в Gin нет встроенной поддержки миграций баз данных. Изменениями схемы БД разработчики управляют вручную, из-за чего потенциально увеличиваются сложность и время разработки.

Стандарты REST не соблюдаются. В Gin нет автоматического генерирования обработчиков CRUD для сущностей, определять конечные точки и выполнять операции CRUD приходится вручную. Это чревато расхождениями при проектировании конечных точек, увеличением затрат на разработку.

Нет поддержки трассировки и метрик по умолчанию, поэтому Gin требуется интеграция со сторонними библиотеками или службами мониторинга производительности приложений.

В целом, несмотря на гибкость и простоту Gin, разработчикам здесь могут потребоваться дополнительные время и усилия на внедрение важного функционала: миграции БД, соблюдение стандартов REST, трассировка, метрики, проверки работоспособности. Функциональность же GoFr всеобъемлюща, все доступно по умолчанию.

Хотя часть этого функционала реализуется в проектах Gin, для ее внедрения разработчикам приходится предпринимать дополнительные шаги.

Благодаря включению важного функционала в GoFr по умолчанию, значительно повышается продуктивность разработчиков, которые стремятся эффективно создавать готовые к промышленной эксплуатации приложения, сокращается время вывода таких приложений на рынок.

С готовой функциональностью упрощаются процессы разработки: не тратя время на интеграцию необходимых компонентов, можно сосредоточиться на создании надежных приложений.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Aryan Mehrotra: GoFr vs Gin: A Comparative Study for Developer Productivity and Production-Ready Applications

Предыдущая статьяШпаргалка по хукам в React
Следующая статьяКогда не стоит использовать метод find() в JavaScript