В этой статье мы сосредоточимся на пошаговой реализации создания REST API с использованием архитектурного шаблона Model-View-Controller (MVC) в Golang.

В шаблоне MVC модель представляет данные и бизнес-логику,представление представляет уровень представления, а Контроллер действует как посредник между Моделью и Представлением, получая данные от пользователя и соответствующим образом обновляя Модель и Представление. Компоненты Repository и Service часто используются для взаимодействия с источником данных и выполнения дополнительной бизнес-логики. .
В целом шаблон MVC помогает разделить задачи приложения на отдельные компоненты, что может сделать код более организованным и более простым в обслуживании.

Создайте новый проект и инициализируйте новый модуль Go:

$ mkdir myapi
$ cd myapi
$ go mod init github.com/myuser/myapi

Давайте начнем с создания нашей структуры каталогов:В этом примере у нас есть шесть каталогов: контроллеры, фиктивные данные, модели, репозиторий, маршруты и сервис, а также один основной файл main.go.

├── controllers
│   └── user_controller.go
├── mockdata
│   └── user_data.go
├── models
│   └── user_model.go
├── repository
│   └── user_repository.go
├── routes
│   └── user_routes.go
├── service
│   └── user_service.go
└── main.go

Далее давайте определим нашу модель. В нашей модели мы определяем нашу структуру Пользователь, которая имеет четыре поля: идентификатор, имя, фамилия, адрес электронной почты и возраст. Мы также создаем пустой фрагмент структур User с именем Users.

package models

type User struct {
 ID        int64  `json:"id"`
 FirstName string `json:"first_name"`
 LastName  string `json:"last_name"`
 Email     string `json:"email"`
 Age       int16  `json:"age"`
}

// This will serve as our database
var Users []User

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

package mockdata
import "restapi.com/myuser/myapi/models"
var Users = []models.User{
 {
  ID:        1,
  FirstName: "Pooja",
  LastName:  "Varma",
  Email:     "[email protected]",
  Age:       25,
 },
 {
  ID:        2,
  FirstName: "pooja",
  LastName:  "varma",
  Email:     "[email protected]",
  Age:       30,
 },
}

Теперь давайте определим наш уровень репозитория среализацией интерфейса UserRepository в Golang, который использует фиктивные данные:
В этом примере мы определили структуру UserRepository, которая содержит фрагмент объектов User для представления фиктивных пользовательских данных. Структура UserRepository реализует методы интерфейса UserRepository (GetAllUsers, GetUserByID, CreateUser, UpdateUser и DeleteUser) для упрощения операций CRUD с пользовательскими данными.
В этом примере мы имеем дело с фиктивными данными, но здесь вы можете подключиться к базе данных и выполнять операции с базой данных.

// repository/user_repository.go
package repository

import (
 "restapi.com/myuser/myapi/mockdata"
 "restapi.com/myuser/myapi/models"
)

// UserRepository interface
type UserRepository interface {
 GetAllUsers() ([]models.User, error)
 GetUserByID(id int64) (models.User, error)
 CreateUser(user models.User) (int64, error)
 UpdateUser(id int64, user models.User) error
 DeleteUser(id int64) error
}

// Mock data to use during testing
// UserRepository struct
type userRepository struct {
}

// NewUserRepository function returns a new UserRepository instance with mock data
func NewUserRepository() UserRepository {
 return &userRepository{}
}

// GetAllUsers function returns all users
func (*userRepository) GetAllUsers() ([]models.User, error) {
 return mockdata.Users, nil
}

// GetUserByID function returns a user by ID
func (*userRepository) GetUserByID(id int64) (models.User, error) {
 for _, user := range mockdata.Users {
  if user.ID == id {
   return user, nil
  }
 }
 return models.User{}, nil
}

// CreateUser function creates a new user
func (*userRepository) CreateUser(user models.User) (int64, error) {
 user.ID = int64(len(mockdata.Users) + 1)
 mockdata.Users = append(mockdata.Users, user)
 return user.ID, nil
}

// UpdateUser function updates an existing user
func (*userRepository) UpdateUser(id int64, user models.User) error {
 for i, u := range mockdata.Users {
  if u.ID == id {
   user.ID = id
   mockdata.Users[i] = user

   return nil
  }
 }
 return nil
}

// DeleteUser function deletes a user by ID
func (r *userRepository) DeleteUser(id int64) error {
 for i, user := range mockdata.Users {
  if user.ID == id {
   mockdata.Users = append(mockdata.Users[:i], mockdata.Users[i+1:]...)
   break
  }
 }
 return nil
}

Вот пример реализации UserService в Golang. В этом примере мы определили структуру UserService, содержащую ссылку на интерфейс UserRepository. Структура UserService реализует методы для облегчения операций CRUD с пользовательскими данными путем вызова соответствующих методов интерфейса UserRepository. К этому уровню можно добавить бизнес-логику.

// service/user_service.go
package service

import (
 "restapi.com/myuser/myapi/models"
 "restapi.com/myuser/myapi/repository"
)

// UserRepository interface
type UserService interface {
 GetAllUsers() ([]models.User, error)
 GetUserByID(id int64) (models.User, error)
 CreateUser(user models.User) (int64, error)
 UpdateUser(id int64, user models.User) error
 DeleteUser(id int64) error
}

// UserService struct
type userService struct {
}

func NewUserService() UserService {
 return &userService{}
}
// Variable of UserRepository
var (
 s repository.UserRepository = repository.NewUserRepository()
)

// GetUsers function returns all users
func (*userService) GetAllUsers() ([]models.User, error) {
 return s.GetAllUsers()
}

// GetUserByID function returns a user by ID
func (*userService) GetUserByID(id int64) (models.User, error) {
 return s.GetUserByID(id)
}

// CreateUser function creates a new user
func (*userService) CreateUser(user models.User) (int64, error) {
 return s.CreateUser(user)
}

// UpdateUser function updates an existing user
func (*userService) UpdateUser(id int64, user models.User) error {
 return s.UpdateUser(id, user)
}

// DeleteUser function deletes a user by ID
func (*userService) DeleteUser(id int64) error {
 return s.DeleteUser(id)
}

Далее давайте определим наш контроллер: в нашем контроллере мы определяем пять функций: GetUsers, GetUserByID, CreateUser, UpdateUser и DeleteUser. Эти функции обрабатывают HTTP-запросы.

// controllers/user_controller.go
package controllers

import (
 "encoding/json"
 "net/http"
 "strconv"
 "github.com/gorilla/mux"
 "restapi.com/myuser/myapi/models"
 "restapi.com/myuser/myapi/service"
)
//creating user_service variable
var (
 c service.UserService = service.NewUserService()
)

// GetUsers function handles GET /users requests
func GetUsers(w http.ResponseWriter, r *http.Request) {
//calling GetAllUsers method from UserService
 users, err := c.GetAllUsers()

 if err != nil {
  w.WriteHeader(http.StatusInternalServerError)
  return
 }

 json.NewEncoder(w).Encode(users)
}

// GetUserByID function handles GET /users/{id} requests
func GetUserByID(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 id, _ := strconv.ParseInt(vars["id"], 10, 64)

 user, err := c.GetUserByID(id)

 if err != nil {
  w.WriteHeader(http.StatusNotFound)
  return
 }

 json.NewEncoder(w).Encode(user)
}

// CreateUser function handles POST /users requests
func CreateUser(w http.ResponseWriter, r *http.Request) {
 var user models.User
 err := json.NewDecoder(r.Body).Decode(&user)

 if err != nil {
  w.WriteHeader(http.StatusBadRequest)
  return
 }
 id, err := c.CreateUser(user)

 if err != nil {
  w.WriteHeader(http.StatusInternalServerError)
  return
 }
 w.WriteHeader(http.StatusCreated)
 json.NewEncoder(w).Encode(map[string]int64{"id": id})
}

// UpdateUser function handles PUT /users/{id} requests
func UpdateUser(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 id, _ := strconv.ParseInt(vars["id"], 10, 64)

 var user models.User
 err := json.NewDecoder(r.Body).Decode(&user)

 if err != nil {
  w.WriteHeader(http.StatusBadRequest)
  return
 }
 err = c.UpdateUser(id, user)
 if err != nil {
  w.WriteHeader(http.StatusNotFound)
  return
 }
 w.WriteHeader(http.StatusNoContent)
}

// DeleteUser function handles DELETE /users/{id} requests
func DeleteUser(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 id, _ := strconv.ParseInt(vars["id"], 10, 64)
 err := c.DeleteUser(id)

 if err != nil {
  w.WriteHeader(http.StatusNotFound)
  return
 }
 w.WriteHeader(http.StatusNoContent)
}

Затем давайте определим нашуреализацию пакета маршрутов для определения логики маршрутизации для пользовательских HTTP-запросов с использованием Golang и пакета мультиплексирования Gorilla:
В этом примере мы определили функцию SetupRoutes, которая создает новый маршрутизатор с помощью пакета мультиплексирования Gorilla и определяет маршруты для пользовательских HTTP-запросов, используя соответствующие функции контроллера из пакета контроллера. Маршрутизатор также определяет маршрут по умолчанию для всех остальных HTTP-запросов.

// routes/routes.go
package routes
import (
 "net/http"
 "restapi.com/myuser/myapi/controllers"
 "github.com/gorilla/mux"
)
func SetupRoutes() *mux.Router {
 router := mux.NewRouter()
 // Define routes for user-related HTTP requests
 router.HandleFunc("/users", controllers.GetUsers).Methods("GET")
 router.HandleFunc("/users/{id}", controllers.GetUserByID).Methods("GET")
 router.HandleFunc("/users", controllers.CreateUser).Methods("POST")
 router.HandleFunc("/users/{id}", controllers.UpdateUser).Methods("PUT")
 router.HandleFunc("/users/{id}", controllers.DeleteUser).Methods("DELETE")
 // Define default route for all other HTTP requests
 router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("Welcome to my API!"))
 })
 return router
}

Теперь, наконец, реализация основного пакета, который создает новый экземпляр маршрутизатора, определенного в пакете маршрутов, и запускает веб-сервер с помощью http.ListenAndServe() функция

// main.go
package main

import (
 "log"
 "net/http"
 "restapi.com/myuser/myapi/routes"
)

func main() {
 router := routes.SetupRoutes()

 log.Println("Starting server on :8080...")
 err := http.ListenAndServe(":8080", router)
 if err != nil {
  log.Fatal(err)
 }
}

В этом примере мы создали новый экземпляр маршрутизатора, определенного в пакете маршрутов, с помощью функции SetupRoutes и запустили веб-сервер с помощью функции http.ListenAndServe(). Сервер прослушивает входящие HTTP-запросы через порт 8080.

Теперь выполните приведенную ниже команду там, где находится проект, т. е. перейдите в корневой каталог вашего проекта Go, который содержит файл go.mod.

$ cd myapi
$ go mod tidy

go mod tidy — это команда в Go, которая автоматически обновляет зависимости модулей проекта Go до версий, указанных в файле go.mod.

Наконец, запустите приложение, выполнив

$ go run main.go

После выполнения команды go run main.go сервер запустился на порте 8080. Протестируйте REST API с помощью postman или любого другого подходящего инструмента.

В заключение, реализация REST API с использованием шаблона проектирования Go Lang MVC может стать отличным способом создания масштабируемого и удобного в сопровождении веб-приложения. Шаблон проектирования Model-View-Controller (MVC) позволяет разработчикам разделять задачи и организовывать код таким образом, чтобы его было легко понять и модифицировать.

Используя встроенные функции Go Lang, такие как пакет net/http и мультиплексор Gorilla, разработчики могут легко создавать быстрые, эффективные и гибкие REST API. Внедряя правильную маршрутизацию, обработчики и промежуточное ПО, разработчики могут обеспечить безопасность и надежность своих конечных точек API.

Кроме того, поддержка параллелизма в Go Lang позволяет создавать высокопроизводительные и масштабируемые веб-приложения. Разработчики могут использовать go-процедуры и каналы для обработки больших объемов запросов и обеспечения гибкости и надежности своего API.

В целом, использование шаблона проектирования Go Lang MVC для реализации REST API может стать эффективным способом создания современных веб-приложений, которые легко поддерживать, расширять и масштабировать.

Вы можете найти исходный код этого урока здесь.