Почему Go — идеальный язык для API?
Когда речь заходит о создании высоконагруженных API, Go (Golang) уверенно занимает место в топе. Его компилируемая природа, встроенная поддержка конкурентности через горутины и минималистичный синтаксис позволяют писать быстрые и надежные микросервисы. Но какой протокол выбрать для вашего следующего проекта: классический REST, гибкий GraphQL или сверхбыстрый gRPC?
В этой статье мы не просто перечислим различия — мы напишем реальный код на Go для каждого подхода, разберем бенчмарки и дадим четкие критерии выбора. Поехали!
REST API на Go: проверенная классика
REST (Representational State Transfer) остается стандартом де-факто для публичных API. Go предлагает отличную стандартную библиотеку net/http, а также популярные фреймворки вроде gin и echo.
Пример REST-сервера на чистом Go
Начнем с простого CRUD-сервиса для управления задачами. Вот как выглядит минимальный REST API на Go без сторонних зависимостей:
package main
import (
"encoding/json"
"log"
"net/http"
"sync"
)
type Task struct {
ID int `json:"id"`
Name string `json:"name"`
}
var (
tasks = []Task{{ID: 1, Name: "Изучить Go"}}
mu sync.Mutex
nextID = 2
)
func getTasks(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tasks)
}
func createTask(w http.ResponseWriter, r *http.Request) {
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
mu.Lock()
task.ID = nextID
nextID++
tasks = append(tasks, task)
mu.Unlock()
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(task)
}
func main() {
http.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getTasks(w, r)
case http.MethodPost:
createTask(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
log.Println("REST API запущен на :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Плюсы REST на Go:
- Простота и предсказуемость — каждый эндпоинт возвращает фиксированную структуру данных
- Отличная кэшируемость через HTTP-заголовки
- Широкая поддержка инструментов (Swagger, Postman)
Минусы: Over-fetching и under-fetching данных. Если клиенту нужно только имя задачи, он всё равно получит полный JSON.
GraphQL на Go: гибкость запросов
GraphQL решает проблему избыточности данных. Клиент сам решает, какие поля ему нужны. Go-сообщество предлагает мощную библиотеку gqlgen, которая генерирует код из схемы.
Пример GraphQL-сервера с gqlgen
Сначала определим схему schema.graphql:
type Task {
id: ID!
name: String!
}
type Query {
tasks: [Task!]!
}
type Mutation {
createTask(name: String!): Task!
}
Теперь запустим генерацию и напишем резолверы на Go:
package main
import (
"log"
"net/http"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
)
// Предполагаем, что сгенерированный код уже есть
type Resolver struct {
tasks []Task
mu sync.Mutex
}
func (r *Resolver) Tasks(ctx context.Context) ([]Task, error) {
r.mu.Lock()
defer r.mu.Unlock()
return r.tasks, nil
}
func (r *Resolver) CreateTask(ctx context.Context, name string) (*Task, error) {
r.mu.Lock()
defer r.mu.Unlock()
task := Task{ID: strconv.Itoa(len(r.tasks) + 1), Name: name}
r.tasks = append(r.tasks, task)
return &task, nil
}
func main() {
srv := handler.NewDefaultServer(
NewExecutableSchema(Config{Resolvers: &Resolver{}}),
)
http.Handle("/query", srv)
http.Handle("/", playground.Handler("GraphQL", "/query"))
log.Println("GraphQL API запущен на :8081")
log.Fatal(http.ListenAndServe(":8081", nil))
}
Ключевые особенности GraphQL на Go:
- Один эндпоинт вместо множества — весь API доступен через POST /query
- Автоматическая генерация типов и резолверов из схемы
- Встроенная документация через GraphQL Playground
GraphQL идеален для сложных клиентских приложений (React, Vue, мобильные приложения), где нужно точно контролировать, какие данные приходят с сервера.
gRPC на Go: скорость и строгая типизация
gRPC — это не просто API, а целый фреймворк от Google, основанный на HTTP/2 и Protobuf. Он обеспечивает бинарную сериализацию и поддержку стримов. Для Go это нативный протокол — gRPC написан на Go.
Пример gRPC-сервиса на Go
Сначала определим .proto файл:
syntax = "proto3";
package task;
service TaskService {
rpc GetTasks (Empty) returns (TaskList);
rpc CreateTask (CreateTaskRequest) returns (Task);
}
message Task {
int32 id = 1;
string name = 2;
}
message TaskList {
repeated Task tasks = 1;
}
message CreateTaskRequest {
string name = 1;
}
message Empty {}
Генерируем код через protoc, затем пишем сервер:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/your/protobuf/task"
)
type server struct {
pb.UnimplementedTaskServiceServer
tasks []*pb.Task
}
func (s *server) GetTasks(ctx context.Context, _ *pb.Empty) (*pb.TaskList, error) {
return &pb.TaskList{Tasks: s.tasks}, nil
}
func (s *server) CreateTask(ctx context.Context, req *pb.CreateTaskRequest) (*pb.Task, error) {
task := &pb.Task{
Id: int32(len(s.tasks) + 1),
Name: req.Name,
}
s.tasks = append(s.tasks, task)
return task, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterTaskServiceServer(s, &server{})
log.Println("gRPC сервер запущен на :50051")
s.Serve(lis)
}
Почему gRPC выигрывает в производительности:
- Protobuf в 3-10 раз быстрее JSON при сериализации
- HTTP/2 позволяет мультиплексировать запрос