Полноценную микросервисную систему: API Gateway, сервис заказов, сервис платежей, сервис уведомлений.
Стек: Go + gRPC + Kafka + PostgreSQL + Redis + Docker + Prometheus + Jaeger.
Все паттерны из 39 уроков в одном проекте!
┌──────────────────────────────────────────────────────────────────┐ │ КЛИЕНТ (браузер / мобильное приложение) │ └──────────────────────────────┬───────────────────────────────────┘ │ HTTP REST ▼ ┌──────────────────────────────────────────────────────────────────┐ │ API GATEWAY (:8080) │ │ • Принимает HTTP-запросы │ │ • Валидация (go-playground/validator) │ │ • Аутентификация (JWT) │ │ • Rate limiting │ │ • Проксирует в gRPC-сервисы │ └──────┬──────────────┬──────────────┬─────────────────────────────┘ │ gRPC │ gRPC │ gRPC ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ ORDER │ │ PAYMENT │ │ USER │ │ SERVICE │ │ SERVICE │ │ SERVICE │ │ (:50051) │ │ (:50052) │ │ (:50053) │ │ │ │ │ │ │ │ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │ └─────┬──────┘ └─────┬──────┘ └────────────┘ │ Kafka │ Kafka ▼ ▼ ┌──────────────────────────────────────────┐ │ KAFKA │ │ order.created, payment.completed, … │ └────────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ NOTIFICATION SERVICE │ │ • Слушает Kafka │ │ • Отправляет email/push │ │ • Redis для идемпотентности │ └──────────────────────────────────────────┘
┌──────────────────────────────────────────┐ │ ИНФРАСТРУКТУРА │ │ • Prometheus (метрики) │ │ • Jaeger (трассировка) │ │ • Docker Compose (локально) │ │ • Kubernetes (production) │ └──────────────────────────────────────────┘
mkdir final-project && cd final-project
# Создаём все сервисы
mkdir -p api-gateway/{cmd/server,internal/{handler,middleware,config},proto}
mkdir -p order-service/{cmd/server,internal/{service,repository,domain},proto}
mkdir -p payment-service/{cmd/server,internal/{service,repository,domain},proto}
mkdir -p notification-service/{cmd/consumer,internal/{consumer,email,idempotency}}
mkdir -p proto/{order,payment,user}
mkdir -p k8s
mkdir -p docker
docker/docker-compose.ymlversion: '3.9'
services:
# ==========================================
# API Gateway
# ==========================================
api-gateway:
build:
context: ..
dockerfile: docker/Dockerfile.api-gateway
ports: ["8080:8080"]
environment:
- ORDER_SERVICE_ADDR=order-service:50051
- PAYMENT_SERVICE_ADDR=payment-service:50052
- JWT_SECRET=${JWT_SECRET}
depends_on: [order-service, payment-service]
# ==========================================
# Order Service
# ==========================================
order-service:
build:
context: ..
dockerfile: docker/Dockerfile.service
environment:
- DATABASE_URL=postgres://postgres:secret@order-db:5432/orders?sslmode=disable
- KAFKA_BROKERS=kafka:9092
depends_on:
order-db: {condition: service_healthy}
kafka: {condition: service_healthy}
order-db:
image: postgres:16-alpine
environment:
POSTGRES_DB: orders
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
# ==========================================
# Payment Service
# ==========================================
payment-service:
build:
context: ..
dockerfile: docker/Dockerfile.service
environment:
- DATABASE_URL=postgres://postgres:secret@payment-db:5432/payments?sslmode=disable
- KAFKA_BROKERS=kafka:9092
depends_on:
payment-db: {condition: service_healthy}
kafka: {condition: service_healthy}
payment-db:
image: postgres:16-alpine
environment:
POSTGRES_DB: payments
POSTGRES_PASSWORD: secret
# ==========================================
# Notification Service
# ==========================================
notification-service:
build:
context: ..
dockerfile: docker/Dockerfile.notification
environment:
- KAFKA_BROKERS=kafka:9092
- REDIS_URL=redis://redis:6379/0
depends_on: [kafka, redis]
# ==========================================
# Инфраструктура
# ==========================================
kafka:
image: confluentinc/cp-kafka:7.5.0
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
depends_on: [zookeeper]
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
prometheus:
image: prom/prometheus
ports: ["9090:9090"]
volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml"]
jaeger:
image: jaegertracing/all-in-one:latest
ports: ["16686:16686", "14268:14268"]
api-gateway/cmd/server/main.gopackage main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb_order "final-project/proto/order"
pb_payment "final-project/proto/payment"
"final-project/api-gateway/internal/handler"
"final-project/api-gateway/internal/middleware"
)
func main() {
log.Println("🚀 API Gateway запускается...")
// Подключение к gRPC-сервисам
orderConn, _ := grpc.Dial(
os.Getenv("ORDER_SERVICE_ADDR"),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
grpc.WithTimeout(5*time.Second),
)
defer orderConn.Close()
paymentConn, _ := grpc.Dial(
os.Getenv("PAYMENT_SERVICE_ADDR"),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
grpc.WithTimeout(5*time.Second),
)
defer paymentConn.Close()
orderClient := pb_order.NewOrderServiceClient(orderConn)
paymentClient := pb_payment.NewPaymentServiceClient(paymentConn)
// Инициализация JWT-сервиса
jwtService := auth.NewJWTService(
os.Getenv("JWT_SECRET"),
15*time.Minute,
7*24*time.Hour,
)
// Создание обработчиков
orderHandler := handler.NewOrderHandler(orderClient, paymentClient)
// Настройка роутера
mux := http.NewServeMux()
// Health
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
// Метрики Prometheus
mux.Handle("/metrics", promhttp.Handler())
// API v1
apiV1 := http.NewServeMux()
apiV1.HandleFunc("POST /api/v1/orders", orderHandler.CreateOrder)
apiV1.HandleFunc("GET /api/v1/orders/{id}", orderHandler.GetOrder)
apiV1.HandleFunc("POST /api/v1/login", orderHandler.Login)
mux.Handle("/api/v1/", apiV1)
// Middleware
handler := middleware.Chain(
middleware.RequestID,
middleware.Logging,
middleware.Recovery,
middleware.Metrics,
middleware.CORS([]string{"*"}),
middleware.RateLimit(100, 200),
)(mux)
server := &http.Server{Addr: ":8080", Handler: handler}
go func() {
log.Println("✅ Gateway на :8080")
server.ListenAndServe()
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
}
proto/order/order.protosyntax = "proto3";
package order;
option go_package = "final-project/proto/order;orderpb";
import "google/protobuf/timestamp.proto";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse);
}
message CreateOrderRequest {
string user_id = 1;
repeated OrderItem items = 2;
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
int64 price = 3; // в центах
}
message CreateOrderResponse {
string id = 1;
int64 total_price = 2;
string status = 3;
}
message GetOrderRequest { string id = 1; }
message GetOrderResponse {
string id = 1;
string user_id = 2;
repeated OrderItem items = 3;
int64 total_price = 4;
string status = 5;
google.protobuf.Timestamp created_at = 6;
}
message CancelOrderRequest { string id = 1; }
message CancelOrderResponse { bool success = 1; }
order-service/internal/service/order_service.gopackage service
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"final-project/order-service/internal/domain"
"final-project/order-service/internal/kafka"
)
type OrderService struct {
repo domain.OrderRepository
producer *kafka.Producer
}
func NewOrderService(repo domain.OrderRepository, producer *kafka.Producer) *OrderService {
return &OrderService{repo: repo, producer: producer}
}
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderReq) (*domain.Order, error) {
// Валидация
if req.UserID == "" {
return nil, status.Error(codes.InvalidArgument, "user_id required")
}
if len(req.Items) == 0 {
return nil, status.Error(codes.InvalidArgument, "at least one item required")
}
// Рассчитываем сумму
var totalPrice int64
for _, item := range req.Items {
totalPrice += item.Price * int64(item.Quantity)
}
// Создаём заказ в БД
order := &domain.Order{
ID: uuid.New().String(),
UserID: req.UserID,
Items: req.Items,
TotalPrice: totalPrice,
Status: "pending",
CreatedAt: time.Now(),
}
if err := s.repo.Create(ctx, order); err != nil {
return nil, fmt.Errorf("create order: %w", err)
}
// Публикуем событие в Kafka
s.producer.PublishEvent(ctx, "order.created", map[string]interface{}{
"order_id": order.ID,
"user_id": order.UserID,
"total_price": order.TotalPrice,
"status": order.Status,
"created_at": order.CreatedAt,
})
return order, nil
}
func (s *OrderService) GetOrder(ctx context.Context, id string) (*domain.Order, error) {
order, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, status.Error(codes.NotFound, "order not found")
}
return order, nil
}
func (s *OrderService) CancelOrder(ctx context.Context, id string) error {
if err := s.repo.UpdateStatus(ctx, id, "cancelled"); err != nil {
return fmt.Errorf("cancel order: %w", err)
}
s.producer.PublishEvent(ctx, "order.cancelled", map[string]string{
"order_id": id,
})
return nil
}
type CreateOrderReq struct {
UserID string
Items []domain.OrderItem
}
notification-service/internal/consumer/order_consumer.gopackage consumer
import (
"context"
"encoding/json"
"log"
"final-project/notification-service/internal/email"
"final-project/notification-service/internal/idempotency"
"github.com/segmentio/kafka-go"
)
type OrderConsumer struct {
reader *kafka.Reader
idemp *idempotency.RedisStore
emailSvc *email.Service
}
func NewOrderConsumer(
brokers []string,
idemp *idempotency.RedisStore,
emailSvc *email.Service,
) *OrderConsumer {
reader := kafka.NewReader(kafka.ReaderConfig{
Brokers: brokers,
Topic: "order.created",
GroupID: "notification-service",
CommitInterval: -1, // Ручной коммит
})
return &OrderConsumer{reader: reader, idemp: idemp, emailSvc: emailSvc}
}
func (c *OrderConsumer) Start(ctx context.Context) error {
log.Println("📥 Consumer запущен: order.created")
for {
msg, err := c.reader.FetchMessage(ctx)
if err != nil {
if err == context.Canceled {
return nil
}
log.Printf("Ошибка чтения: %v", err)
continue
}
// Декодируем событие
var event struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Amount int64 `json:"total_price"`
}
json.Unmarshal(msg.Value, &event)
// Проверяем идемпотентность
isDup, _, _ := c.idemp.IsDuplicate(ctx, msg.Key)
if isDup {
log.Printf("Дубликат: %s", string(msg.Key))
c.reader.CommitMessages(ctx, msg)
continue
}
// Отмечаем как "в обработке"
c.idemp.MarkProcessing(ctx, string(msg.Key))
// Отправляем email
err = c.emailSvc.SendOrderConfirmation(ctx, event.UserID, event.OrderID)
if err != nil {
log.Printf("Ошибка отправки email: %v", err)
// Не коммитим — попробуем снова
continue
}
// Отмечаем как выполненный
c.idemp.MarkCompleted(ctx, string(msg.Key), "sent")
// Коммитим
c.reader.CommitMessages(ctx, msg)
log.Printf("✅ Уведомление отправлено для заказа %s", event.OrderID)
}
}
func (c *OrderConsumer) Close() error {
return c.reader.Close()
}
k8s/deployment.yaml (Kubernetes)apiVersion: apps/v1
kind: Deployment
metadata: {name: order-service}
spec:
replicas: 3
selector:
matchLabels: {app: order-service}
template:
metadata:
labels: {app: order-service}
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
spec:
containers:
- name: order-service
image: ghcr.io/myorg/order-service:latest
ports:
- {containerPort: 50051, name: grpc}
- {containerPort: 9090, name: metrics}
envFrom:
- configMapRef: {name: order-service-config}
- secretRef: {name: order-service-secrets}
resources:
requests: {cpu: "50m", memory: "64Mi"}
limits: {cpu: "200m", memory: "256Mi"}
livenessProbe:
grpc: {port: 50051}
initialDelaySeconds: 5
readinessProbe:
grpc: {port: 50051}
initialDelaySeconds: 2
---
apiVersion: v1
kind: Service
metadata: {name: order-service}
spec:
selector: {app: order-service}
ports:
- {port: 50051, name: grpc}
- {port: 9090, name: metrics}
# 1. Клонируем / создаём проект
# (все файлы из этого урока)
# 2. Генерируем proto-код
for proto in proto/*/; do
protoc --go_out=. --go-grpc_out=. ${proto}*.proto
done
# 3. Запускаем docker-compose
cd docker
docker-compose up -d
# 4. Проверяем
curl http://localhost:8080/health
curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-d '{"user_id":"user-1","items":[{"product_id":"prod-1","quantity":2,"price":9999}]}'
# 5. Метрики
curl http://localhost:8080/metrics | grep orders_created_total
# 6. Jaeger
open http://localhost:16686
# 7. Prometheus
open http://localhost:9090
# 8. Деплой в K8s
kubectl apply -f k8s/
Что дальше?
Практикуйся: перепиши свой Node.js микросервис на Go
Углубляйся: изучай исходники стандартной библиотеки
Контрибьють: открытые Go-проекты на GitHub
Читай: “100 Go Mistakes” — must-read для каждого Go-разработчика
Помни: Go — это простота. Не усложняй.
💡 Для Node.js разработчика (финальные мысли):