Irbisa · Шпаргалка

Архитектура и паттерны в Go

SOLID в Go, чистая и гексагональная архитектура, строительные блоки DDD, CQRS, паттерны в стиле Go, внедрение зависимостей

Middle-разработчик · 17 вопросов

  1. 01

    Что говорят пять принципов SOLID и как каждый из них выглядит в Go, где нет классов и наследования?

    SOLID объединяет пять правил: единственная ответственность, открытость/закрытость, подстановка Лисков, разделение интерфейсов и инверсия зависимостей.

  2. 02

    Что означают DRY, KISS и YAGNI и когда устранение дублирования делает код на Go хуже?

    DRY (don't repeat yourself) требует, чтобы каждое знание жило в одном месте, KISS (keep it simple) велит выбирать самое простое работающее решение, а YAGNI (you aren't gonna need it) запрещает строить под воображаемые потребности.

  3. 03

    Что означают слабая связанность (low coupling) и сильная связность (high cohesion) и как по импортам и экспортируемым именам пакета Go видно, что их нет?

    Связанность (coupling) показывает, насколько пакет зависит от деталей других; связность (cohesion) показывает, насколько код внутри одного пакета относится к одному делу.

  4. 04

    ReadOnlyCache реализует Cache, возвращая ошибку из Set. Какой принцип SOLID он нарушает и как исправить дизайн?

    type Cache interface {	Get(ctx context.Context, key string) ([]byte, bool)	Set(ctx context.Context, key string, val []byte) error} var ErrReadOnly = errors.New("cache is read-only") // Serves a snapshot embedded in the binary.type ReadOnlyCache struct{ data map[string][]byte } func (c *ReadOnlyCache) Get(_ context.Context, key string) ([]byte, bool) {	v, ok := c.data[key]	return v, ok} func (c *ReadOnlyCache) Set(context.Context, string, []byte) error { return ErrReadOnly }

    Нарушен принцип подстановки Лисков: код, который работает с Cache, ожидает, что Set сохранит значение, а ReadOnlyCache компилируется, но нарушает это обещание в рантайме.

  5. 05

    Чем отличаются инверсия зависимостей (DIP), внедрение зависимостей (DI) и инверсия управления (IoC) и что из этого использует main на Go, собранный вручную?

    Инверсия зависимостей задаёт правило: бизнес-код зависит от интерфейса, которым владеет сам.

  6. 06

    Как в Go делают внедрение зависимостей и нужен ли для этого фреймворк вроде wire или fx?

    Передавайте зависимости явно (параметрами конструктора, которые сохраняются в полях структуры) и собирайте весь граф вручную в main.

    package main import (	"context"	"database/sql"	"log"…
  7. 07

    Какими классическими паттернами проектирования вы реально пользуетесь в Go, а какие язык делает ненужными или сводит к одной функции?

    Большинство паттернов остаются, но сжимаются до возможности языка: Strategy становится интерфейсом или функцией, Decorator превращается в middleware, Builder в функциональные опции, Singleton в sync.OnceValue, Iterator в iter.Seq.

  8. 08

    Как реализовать паттерн Strategy в Go и когда функциональный тип вроде http.HandlerFunc лучше интерфейса с одним методом?

    Стратегия представляет собой значение, которым вызывающий выбирает поведение: интерфейс с одним методом или просто функция.

    type Order struct {	TotalCents int64	Coupon     string} // The strategy: how the discount is computed.…
  9. 09

    Найдите баг: после того как UserRepo обернули в кэширующий декоратор, UpdateEmail проходит успешно, но Get продолжает возвращать старый email, пока не истечёт TTL.

    type cachedRepo struct {	UserRepo // methods not defined below go straight to the database	cache    *ttlCache[int64, User]} func (r *cachedRepo) Get(ctx context.Context, id int64) (User, error) {	if u, ok := r.cache.Get(id); ok {		return u, nil	}	u, err := r.UserRepo.Get(ctx, id)	if err == nil {		r.cache.Set(id, u)	}	return u, err}

    Декоратор встраивает UserRepo, поэтому UpdateEmail продвигается из внутреннего репозитория: запись уходит прямо в базу и не трогает кэш.

  10. 10

    Что такое Clean Architecture и гексагональная архитектура (порты и адаптеры), что такое правило зависимостей и когда это перебор для сервиса на Go?

    Обе помещают бизнес-правила в центр, а всё техническое (HTTP, SQL, Kafka, сторонние API) на край и подчиняются одному правилу: зависимости в исходном коде направлены только внутрь.

  11. 11

    Из-за чего через интерфейс репозитория протекает база данных (*sql.Rows, куски SQL, модели ORM в сигнатурах) и что он должен отдавать вместо этого?

    Он протекает, когда в его сигнатурах есть типы или понятия хранилища: тогда каждый вызывающий зависит от базы через интерфейс, который должен был её спрятать.

    // wrong: the database leaks through the interface.type LeakyOrderRepo interface {	Query(ctx context.Context, where string, args ...any) (*sql.Rows, error)	Get(ctx context.Context, id int64) (OrderRow, error) // sql.NullString fields inside}…
  12. 12

    Как не пустить типы стороннего SDK (платёжного провайдера, клиента S3) в доменный код и что даёт антикоррупционный слой (anti-corruption layer)?

    Объявите порт в своих терминах (payment.Gateway с вашим типом Charge и ErrDeclined) и реализуйте его в одном пакете-адаптере, единственном, который импортирует SDK и переводит его типы, статусы и ошибки.

    // package payment: the port, in the domain's own terms.var ErrDeclined = errors.New("payment declined") type Charge struct {	AmountCents    int64	Currency       string // ISO 4217: "EUR"…
  13. 13

    Что такое сущность, объект-значение и агрегат в DDD и как в Go защитить инварианты агрегата, если поля структуры экспортируемые?

    У сущности есть идентичность, которая переживает изменения (заказ и его id), объект-значение определяется только значениями и никогда не меняется (Money), а агрегат объединяет их в группу, которую меняют только через корень, хранителя инвариантов.

    // Money is a value object: no identity, compared with ==, never mutated.type Money struct {	Cents    int64	Currency string}…
  14. 14

    В Go нет sealed-классов и enum с данными. Как смоделировать закрытый набор вариантов, например способов оплаты или состояний заказа, чтобы пропущенный случай ловился?

    Возьмите интерфейс с неэкспортируемым методом-маркером: объявить этот метод может только свой пакет, и набор закрыт.

    // Method is a closed set: isMethod is unexported, so no other package can add a variant.////sumtype:decltype Method interface{ isMethod() } type Card struct{ Last4 string }…
  15. 15

    Что такое ограниченный контекст (bounded context) в DDD и как он определяет, где заканчивается один пакет или сервис на Go и начинается следующий?

    Ограниченный контекст задаёт границу, внутри которой у модели и её терминов один смысл: Product в каталоге (описание, фото) не равен Product на складе (вес, полка) или в биллинге (цена, налог).

  16. 16

    Что такое CQRS, когда отдельная модель чтения себя оправдывает и чем за неё платят в согласованности?

    CQRS (command query responsibility segregation) отделяет код, который меняет состояние, от кода, который его читает: команды идут через доменную модель, запросы читают модель под экран.

  17. 17

    Что такое event sourcing, чем он отличается от хранения текущего состояния плюс outbox и что делает его трудным в продакшене?

    В event sourcing источником истины служит журнал доменных событий, куда только дописывают, а текущее состояние получают его воспроизведением.

    type Event interface{ isEvent() } type Placed struct{ ID string }type LineAdded struct{ Cents int64 }type Paid struct{}…