Go: Паттерны и принципы

16 вопросов

1 SOLID в Go. Как применяют.

S - один класс/тип одна ответственность; в Go маленькие интерфейсы и пакеты. O - открыт для расширения, закрыт для изменения; интерфейсы и внедрение зависимостей. L - подтипы заменяемы; в Go нет наследования, но интерфейсы должны быть реализуемы так, чтобы клиент не зависел от конкретной реализации. I - узкие интерфейсы (io.Reader, io.Writer). D - зависимость от абстракций (интерфейсов), не от конкретных типов.

type UserRepository interface { GetByID(ctx context.Context, id int64) (*User, error) }
type Service struct { repo UserRepository }
Открыть отдельно →
2 DRY, KISS, YAGNI в Go.

DRY - не дублировать логику; выносить в функции, пакеты, общие типы. KISS - простые решения; не усложнять без необходимости. YAGNI - не реализовывать функциональность "на будущее". В Go: переиспользование через пакеты и интерфейсы, простые структуры данных, минимальный публичный API. Рефакторинг при появлении реального дублирования.

Открыть отдельно →
3 Clean Architecture в Go. Слои и зависимости.

Слои: entities (доменные сущности), use cases (бизнес-логика), interface adapters (handlers, репозитории), frameworks (HTTP, БД). Зависимости направлены внутрь: use case не зависит от HTTP и БД; адаптеры реализуют интерфейсы, определенные в use case. В Go: пакет domain (сущности и интерфейсы use case), пакет usecase, пакеты delivery (http), repository (postgres). Внедрение через конструктор.

// domain/repository.go
type UserRepository interface { Save(ctx context.Context, u *User) error }
// usecase/user.go
type Service struct { repo UserRepository }
// delivery/http/handler.go
type Handler struct { svc *usecase.Service }
Открыть отдельно →
4 Hexagonal (Ports and Adapters) в Go.

Ядро приложения (домен + use cases) не зависит от внешнего мира. Порты - интерфейсы (входящие: API, входящие сообщения; исходящие: репозиторий, внешние API). Адаптеры реализуют порты: HTTP handler, gRPC server, PostgreSQL репозиторий, HTTP-клиент к другому сервису. В Go порты в пакете domain или app; адаптеры в отдельных пакетах (adapters/http, adapters/postgres). Тестирование ядра с моками адаптеров.

type UserPort interface { GetByID(ctx context.Context, id int64) (*User, error) }
type UserRepositoryPort interface { FindByID(ctx context.Context, id int64) (*User, error) }
Открыть отдельно →
5 Layered (многослойная) архитектура в Go-приложении.

Классическое разделение на горизонтальные слои: presentation (HTTP handlers), business logic (service), data access (repository). Зависимости сверху вниз: handler вызывает service, service вызывает repository. Нижний слой не знает о верхнем. В Go: пакет handler (разбор HTTP, формирование ответов), пакет service (бизнес-правила, валидация, оркестрация), пакет repository (SQL-запросы, работа с БД). Проста в понимании и подходит для большинства CRUD-приложений. Отличие от Clean/Hexagonal: в layered зависимости идут в одну сторону (вниз), но слои могут зависеть от конкретных типов, а не от интерфейсов.

// handler -> service -> repository
type UserHandler struct { svc *UserService }
type UserService struct { repo *UserRepo }
type UserRepo struct { db *pgxpool.Pool }
Открыть отдельно →
6 DDD основы в Go. Entity, Value Object, Aggregate.

Entity - идентичность по id (User). Value Object - без идентичности, неизменяемый (Address, Money). Aggregate - группа сущностей с корнем (Order + OrderItems); изменения через корень, граница консистентности. В Go: структуры с id для entity; структуры без id для value object; агрегат - тип с методами и вложенными сущностями, репозиторий по корню агрегата.

type Order struct {
    ID    int64
    Items []OrderItem
}
func (o *Order) AddItem(sku string, qty int) { ... }
Открыть отдельно →
7 Bounded Context в DDD. Как отразить в Go.

Bounded Context - граница модели и языка (ubiquitous language) внутри контекста. Разные контексты могут иметь свои модели с одинаковыми именами (Order в заказах и в доставке). В Go: отдельные пакеты или модули по контекстам (pkg/order, pkg/shipping); обмен между контекстами через события или антикоррупционный слой (DTO, маппинг). Не смешивать модели разных контекстов в одном пакете.

Открыть отдельно →
8 Aggregate и инварианты в Go.

Агрегат - граница консистентности; инварианты соблюдаются внутри агрегата. Все изменения через корень агрегата; репозиторий загружает и сохраняет целый агрегат. В Go корень - структура с методами (AddItem, Confirm), проверки инвариантов внутри методов. Не менять вложенные сущности в обход корня. Транзакция при сохранении одного агрегата.

func (o *Order) Confirm() error {
    if o.Status != OrderDraft { return ErrInvalidState }
    o.Status = OrderConfirmed
    return nil
}
Открыть отдельно →
9 Feature Flags в Go. Реализация и хранение.

Feature flag - переключатель функциональности без деплоя. В Go: проверка флага перед выполнением ветки кода. Хранение: конфиг (файл, env), БД, внешний сервис (LaunchDarkly, Unleash). Реализация: интерфейс IsEnabled(feature, userID) bool, реализация читает из кеша или API. Удобно для постепенного раската и A/B тестов.

if flags.IsEnabled(ctx, "new_checkout", userID) {
    return newCheckout(ctx, ...)
}
return oldCheckout(ctx, ...)
Открыть отдельно →
10 12-Factor App и Go.

Двенадцать факторов: код в Git, зависимости явно (go.mod), конфиг в окружении, бэкенды как присоединяемые ресурсы, стадии сборки/запуска разделены, процессы без состояния, экспорт сервисов через порт, масштаб через процессы, быстрый запуск и graceful shutdown, dev/prod паритет, логи как поток, админ-задачи как разовые процессы. В Go: конфиг из env, один процесс - один бинарник, логи в stdout, миграции и задачи - отдельные команды.

Открыть отдельно →
11 Идемпотентность операций в Go.

Повторное выполнение операции дает тот же результат и не имеет дополнительных побочных эффектов. В Go: проверка по идемпотентному ключу (клиент шлет Idempotency-Key), кеш результата по ключу; при повторном запросе возвращать сохраненный ответ. Для сообщений - дедупликация по message id или бизнес-ключу в БД (INSERT ... ON CONFLICT DO NOTHING или проверка перед действием).

if key := r.Header.Get("Idempotency-Key"); key != "" {
    if cached, ok := cache.Get(key); ok { writeResponse(w, cached); return }
}
resp := doWork()
cache.Set(key, resp, ttl)
Открыть отдельно →
12 Eventual consistency в распределенных системах на Go.

Консистентность достигается со временем после прекращения обновлений. В Go: асинхронная репликация, очереди событий, кеши с TTL. Приложение должно учитывать задержку: не полагаться на немедленное отражение записи в read-репликах; использовать паттерны (read-your-writes через чтение с primary после записи или задержка). Компенсации при конфликтах и дедупликация по ключам.

Открыть отдельно →
13 Composition over inheritance в Go.

В Go нет наследования; композиция через встраивание структур (embedding) и интерфейсы. Поведение переиспользуют, включая типы в структуру и вызывая их методы; при необходимости переопределяют методы. Предпочитают маленькие интерфейсы и композицию больших типов из маленьких. Не эмулировать иерархии классов; использовать интерфейсы и внедрение зависимостей.

type Handler struct {
    Logger  Logger
    Metrics Metrics
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.Logger.Info("request")
    h.next.ServeHTTP(w, r)
}
Открыть отдельно →
14 Dependency Injection в Go. Конструктор и интерфейсы.

Зависимости передаются извне (через конструктор или опции), не создаются внутри типа. В Go: NewService(repo UserRepository, logger Logger) *Service; repo и logger - интерфейсы. Тестирование - подмена моками. Без DI-контейнера вручную собирают граф в main или init. Вариант - functional options: NewService(opts ...Option), WithRepo(r), WithLogger(l).

func NewService(repo UserRepository, log Logger) *Service {
    return &Service{repo: repo, log: log}
}
// test
svc := NewService(mockRepo, noopLogger)
Открыть отдельно →
15 Functional options в Go. Паттерн конфигурации.

Опциональные параметры через функции: Option - тип func(*Config) или интерфейс; конструктор принимает ...Option и применяет их к конфигу. Удобно для большого числа опций и значений по умолчанию. В стандартной библиотеке: grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()).

type Option func(*Server)
func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func NewServer(opts ...Option) *Server {
    s := &Server{port: 8080}
    for _, o := range opts { o(s) }
    return s
}
Открыть отдельно →
16 Table-driven design в Go (не только тесты).

Данные в виде таблицы (слайс структур) и общий код итерации. Используют не только в тестах: маршрутизация по типу (таблица обработчиков), валидация полей (таблица правил), конечные автоматы (таблица переходов). В Go: слайс с полями для входа и выхода или действий; один цикл или lookup. Упрощает добавление кейсов без изменения логики.

var routes = []struct {
    path string
    fn   http.HandlerFunc
}{
    {"/health", healthHandler},
    {"/api/users", usersHandler},
}
for _, r := range routes { mux.Handle(r.path, r.fn) }
Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.