GinJuniorCoding

Как реализовать версионирование API (например, /api/v1/, /api/v2/) с помощью групп маршрутов Gin?

Используйте r.Group("/api/v1") и r.Group("/api/v2") — каждая группа получает свой префикс и может иметь собственные middleware и handler-ы.

Версионирование API через группы маршрутов в Gin

Gin предоставляет метод Group(), который возвращает объект *gin.RouterGroup с общим префиксом пути и набором middleware. Это стандартный способ разграничить версии API без дублирования логики.

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// v1 — стабильная версия
	v1 := r.Group("/api/v1")
	{
		v1.GET("/users", listUsersV1)
		v1.POST("/users", createUserV1)
		v1.GET("/users/:id", getUserV1)
	}

	// v2 — новая версия с другой структурой ответа
	v2 := r.Group("/api/v2")
	{
		v2.GET("/users", listUsersV2)
		v2.POST("/users", createUserV2)
		v2.GET("/users/:id", getUserV2)
	}

	r.Run(":8080")
}

func listUsersV1(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"data": []string{"alice", "bob"}})
}

func listUsersV2(c *gin.Context) {
	// v2 возвращает пагинацию и метаданные
	c.JSON(http.StatusOK, gin.H{
		"data": []string{"alice", "bob"},
		"meta": gin.H{"total": 2, "page": 1},
	})
}

func createUserV1(c *gin.Context) { c.Status(http.StatusCreated) }
func getUserV1(c *gin.Context)    { c.JSON(http.StatusOK, gin.H{"id": c.Param("id")}) }
func createUserV2(c *gin.Context) { c.Status(http.StatusCreated) }
func getUserV2(c *gin.Context)    { c.JSON(http.StatusOK, gin.H{"id": c.Param("id")}) }

Группам можно добавлять свои middleware — например, аутентификацию только для v2:

v2 := r.Group("/api/v2", AuthMiddleware())

Для крупных проектов удобно разносить версии по отдельным пакетам и регистрировать их через функцию RegisterRoutes:

// v1/routes.go
package v1

import "github.com/gin-gonic/gin"

func RegisterRoutes(rg *gin.RouterGroup) {
	rg.GET("/users", ListUsers)
	rg.POST("/users", CreateUser)
}
// main.go
v1Group := r.Group("/api/v1")
v1.RegisterRoutes(v1Group)

v2Group := r.Group("/api/v2")
v2.RegisterRoutes(v2Group)

Такой подход позволяет каждой версии иметь собственные типы запросов/ответов, handler-ы и middleware, не затрагивая другие версии.

Подводные камни

  • Фигурные скобки { } вокруг маршрутов группы — синтаксический сахар Go, они не создают отдельного scope для переменной группы; это только читаемость.
  • Если несколько групп используют один и тот же middleware (например, логирование), лучше навесить его на корневой роутер, а не дублировать в каждой группе.
  • Prefix /api/v1 не добавляет завершающий слэш — маршрут /users даст путь /api/v1/users, а не /api/v1//users; следите за двойными слэшами при конкатенации.
  • Gin не поддерживает negotiation через заголовок Accept-Version из коробки — если нужна версионность через заголовки, реализуйте отдельный middleware.
  • Группы не изолируют глобальные middleware, добавленные через r.Use() до создания группы — они применятся ко всем группам.
  • При большом количестве версий разрастается время компиляции роутинг-дерева; Gin использует radix tree, поэтому N версий с M маршрутами каждая = N*M узлов.
  • Удаление версии из кода не гарантирует возврата 404 клиентам — убедитесь, что nginx или API gateway не кешируют старые маршруты.

Common mistakes

  • Давать ответ про версионирование API через группы маршрутов в Gin только на уровне определения, не показывая поведение в реальном приложении.
  • Игнорировать границы ответственности вокруг темы «версионирование API через группы маршрутов в Gin»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
  • Не связывать версионирование API через группы маршрутов в Gin с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.

What the interviewer is testing

  • Точно объясняет, что именно делает версионирование API через группы маршрутов в Gin и где это используется в Go-коде.
  • Связывает версионирование API через группы маршрутов в Gin с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
  • Не изобретает API и опирается на реальные контракты официальной документации.

Sources

Related topics