Node.js — однопоточный, масштабируется через процессы → Go — многопоточный, масштабируется горутинами
Node.js: NestJS модульный монолит → Go: Modular Monolith с internal/packages
Node.js микросервисы: Express + HTTP → Go: одиночный бинарник 8MB
Node.js: нужно больше инстансов → Go: один инстанс тянет x10 нагрузки
monolithic-app/ ├── cmd/ │ └── server/ │ └── main.go # Точка входа, DI ├── internal/ │ ├── domain/ # Общие типы и интерфейсы │ │ ├── user.go │ │ └── order.go │ ├── users/ # Модуль пользователей │ │ ├── repository.go # Интерфейс репозитория │ │ ├── postgres.go # Реализация │ │ ├── service.go # Бизнес-логика │ │ └── transport.go # HTTP/gRPC хендлеры │ ├── orders/ # Модуль заказов │ │ ├── repository.go │ │ ├── postgres.go │ │ ├── service.go │ │ └── transport.go │ ├── payments/ # Модуль платежей │ │ ├── repository.go │ │ ├── postgres.go │ │ ├── service.go │ │ └── transport.go │ └── shared/ # Общая инфраструктура │ ├── database/ │ ├── logger/ │ └── middleware/ ├── migrations/ └── go.mod
microservices/ ├── api-gateway/ # Gateway (HTTP → gRPC) │ ├── cmd/server/main.go │ └── internal/ ├── user-service/ # Сервис пользователей │ ├── cmd/server/main.go │ ├── internal/ │ └── proto/user.proto ├── order-service/ # Сервис заказов │ ├── cmd/server/main.go │ ├── internal/ │ └── proto/order.proto ├── payment-service/ # Сервис платежей │ ├── cmd/server/main.go │ ├── internal/ │ └── proto/payment.proto ├── notification-service/ # Сервис уведомлений │ ├── cmd/consumer/main.go # Kafka consumer │ └── internal/ ├── docker-compose.yml └── k8s/
// cmd/server/main.go — КОМПОЗИЦИЯ МОДУЛЕЙ
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"monolith/internal/shared/database"
"monolith/internal/users"
"monolith/internal/orders"
"monolith/internal/payments"
)
func main() {
log.Println("🏗️ Запуск Modular Monolith...")
// 1. Инициализация общей инфраструктуры
db := database.MustConnect(os.Getenv("DATABASE_URL"))
defer db.Close()
// 2. Инициализация модулей (каждый независим!)
userModule := users.NewModule(db)
orderModule := orders.NewModule(db)
paymentModule := payments.NewModule(db)
// 3. Связываем модули через ИНТЕРФЕЙСЫ (не прямые вызовы!)
// OrderService нужен UserService для проверки пользователя
orderModule.SetUserService(userModule.Service())
// PaymentService нужен OrderService для получения суммы
paymentModule.SetOrderService(orderModule.Service())
// 4. Настраиваем HTTP-роутер
mux := http.NewServeMux()
userModule.RegisterRoutes(mux) // /api/users/*
orderModule.RegisterRoutes(mux) // /api/orders/*
paymentModule.RegisterRoutes(mux) // /api/payments/*
// 5. Запуск
server := &http.Server{Addr: ":8080", Handler: mux}
go server.ListenAndServe()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
server.Shutdown(context.Background())
}
// ╔══════════════════════════════════════════════════════════╗
// ║ МОДУЛЬ (пример: orders/module.go) ║
// ╚══════════════════════════════════════════════════════════╝
// Module — самодостаточный модуль
type Module struct {
repo OrderRepository
service *OrderService
handler *OrderHandler
userService UserService // Зависимость от другого модуля (ИНТЕРФЕЙС!)
}
// UserService — интерфейс, который нужен от модуля users
type UserService interface {
Exists(ctx context.Context, userID string) (bool, error)
}
func NewModule(db *database.Pool) *Module {
repo := NewPostgresOrderRepo(db)
service := NewOrderService(repo)
handler := NewOrderHandler(service)
return &Module{
repo: repo,
service: service,
handler: handler,
}
}
func (m *Module) Service() *OrderService {
return m.service
}
func (m *Module) SetUserService(us UserService) {
m.service.userService = us
}
func (m *Module) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /api/orders", m.handler.Create)
mux.HandleFunc("GET /api/orders/{id}", m.handler.Get)
}
// ╔══════════════════════════════════════════════════════════╗
// ║ API GATEWAY → gRPC → МИКРОСЕРВИСЫ ║
// ╚══════════════════════════════════════════════════════════╝
// api-gateway/internal/handler/order_handler.go
type OrderHandler struct {
orderClient pb.OrderServiceClient
userClient pb.UserServiceClient
paymentClient pb.PaymentServiceClient
}
func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
var req CreateOrderRequest
json.NewDecoder(r.Body).Decode(&req)
// 1. Проверяем пользователя (gRPC)
userResp, err := h.userClient.GetUser(r.Context(),
&pb.GetUserRequest{Id: req.UserID})
if err != nil {
writeError(w, 404, "User not found")
return
}
// 2. Создаём заказ (gRPC)
orderResp, err := h.orderClient.CreateOrder(r.Context(),
&pb.CreateOrderRequest{
UserID: req.UserID,
Items: convertItems(req.Items),
})
if err != nil {
writeError(w, 500, "Order creation failed")
return
}
// 3. Инициируем платёж (gRPC)
paymentResp, err := h.paymentClient.CreatePayment(r.Context(),
&pb.CreatePaymentRequest{
OrderID: orderResp.Id,
Amount: orderResp.TotalPrice,
})
if err != nil {
// Платёж не прошёл — отменяем заказ (компенсация)
h.orderClient.CancelOrder(r.Context(),
&pb.CancelOrderRequest{Id: orderResp.Id})
writeError(w, 402, "Payment failed")
return
}
writeJSON(w, 201, map[string]string{
"order_id": orderResp.Id,
"payment_id": paymentResp.Id,
})
}
// ╔══════════════════════════════════════════════════════════╗
// ║ КОММУНИКАЦИЯ ЧЕРЕЗ KAFKA (асинхронная) ║
// ╚══════════════════════════════════════════════════════════╝
// order-service — публикует событие
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderReq) (*Order, error) {
order := s.createInDB(ctx, req)
// Публикуем событие
s.kafkaProducer.PublishEvent(ctx, "order.created", OrderCreatedEvent{
OrderID: order.ID,
UserID: order.UserID,
Amount: order.TotalPrice,
})
return order, nil
}
// notification-service — слушает события
func (c *NotificationConsumer) Start(ctx context.Context) {
consumer := kafka.NewConsumer(kafka.ConsumerConfig{
Brokers: []string{"kafka:9092"},
Topic: "order.created",
GroupID: "notification-service",
})
consumer.Start(ctx, func(msg kafka.Message) error {
var event OrderCreatedEvent
json.Unmarshal(msg.Value, &event)
// Отправляем email
return c.emailService.SendOrderConfirmation(event.UserID, event.OrderID)
})
}
| Критерий | Modular Monolith | Микросервисы |
|---|---|---|
| Команда | 1-10 разработчиков | 10-100+ разработчиков |
| Сложность деплоя | Один бинарник | 5-20+ сервисов |
| Производительность | Прямые вызовы (0ms) | Сеть (0.1-5ms) |
| Изоляция ошибок | Падает всё | Падает один сервис |
| Масштабирование | Всё приложение | Проблемный сервис |
| Транзакции | ACID легко | Saga, сложно |
| Стоимость Go-инстанса | 50m CPU, 32MB RAM | 5×50m CPU, 5×32MB RAM |
СТАРТАП (день 1): ┌──────────────────────────────────────┐ │ MODULAR MONOLITH │ │ users | orders | payments | … │ │ (один бинарник, 32MB RAM) │ └──────────────────────────────────────┘ ✅ Быстрая разработка ✅ Простой деплой ✅ Легко тестировать
РОСТ (100+ заказов/сек): ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ users │ │ orders │ │payments│ │ notif │ └────────┘ └────────┘ └────────┘ └────────┘ ↑ ↑ ↑ ↑ └──────────┴──────────┴──────────┘ API Gateway ✅ Независимое масштабирование ✅ Изоляция отказов ✅ Разные команды
КОГДА ДРОБИТЬ:
💡 Best practices от сеньоров:
💡 Для Node.js разработчика: