ГлавнаяБлогАрхитектура Go: почему папки не решают, а зависимости — да
Алгоритмы

Архитектура Go: почему папки не решают, а зависимости — да

Разбираем, почему структура папок в Go не определяет качество кода. Узнайте, как направление зависимостей и интерфейсы формируют настоящую архитектуру. Начните улучшать свой код сегодня!

Al
Редакция Algolitalgolit.ru
8 мин чтения28 августа 2026 г.

Почему папки не создают архитектуру в Go

Критика Go часто звучит так: «Проекты на Go становятся хаотичными, нет фреймворка, который бы направлял. Nest, Django, Spring — они говорят, куда что класть. Go просто говорит: организуй как-нибудь». Это справедливое замечание: Go действительно необычайно снисходителен к структуре. Но я считаю, что винить Go в беспорядке в коде — это как винить пустой документ в плохом эссе. Go не поощряет плохую архитектуру, он её вскрывает.

Идеальная структура папок: миф или реальность?

Спросите сотню Go-разработчиков, куда поместить бизнес-логику, и получите сотню ответов (и 200 мнений). «Нужно ли использовать internal/?», «Всё должно лежать в pkg/?», «Следовать ли чистой архитектуре?», «Что насчёт cmd/?». Мы тратим столько времени на споры о структуре папок, будто расположение директорий определяет качество кода. Как будто переименование utils/ в pkg/shared/ нас спасёт. Боже. Папки не создают архитектуру. Её создают зависимости.

Вы можете тщательно организовать проект так:

my-app/
  cmd/main.go
  internal/
    handler/
    service/
    repository/
  pkg/domain/
  pkg/utils/

И всё равно написать связанный по рукам и ногам мусор. Хендлеры напрямую вызывают репозитории. Сервисы импортируют драйверы баз данных. Бизнес-логика смешана с HTTP-концернами. Всё циклично. Красивые папки, однако. Очень организованно выглядит на GitHub.

Я видел лучшие проекты всего с пятью пакетами, просто они не так хорошо смотрятся на скриншотах.

Архитектура — это направление зависимостей

Архитектура заключается в осознанных решениях о том, как код зависит от другого кода. Взгляните на это:

HTTP Handler
    ↓
Business Service
    ↓
Data Repository

Это не священно из-за названий папок. Это ценно тем, что представляет: хендлер знает только, как переводить HTTP; сервис знает только бизнес-правила; репозиторий знает только, как получать данные. Каждый слой зависит от слоя ниже, никогда не вверх. Этот поток осознанный. Если его перевернуть, всё ломается:

Repository
    ↓
Service
    ↓
Handler

Теперь репозиторию нужно знать об HTTP? Поздравляю, вы изобрели драйвер базы данных, который также говорит на REST, и это, я думаю, крик о помощи. Этот поток работает в проекте из трёх файлов, в монолите или в микросервисе с 50 пакетами. Go не волнует, насколько впечатляюще выглядит ваше дерево в README.

Интерфейсы принадлежат потребителю

Здесь у людей, пришедших с Java, случается небольшой кризис идентичности. В таких языках, как Java, интерфейсы обычно определяются вместе с реализацией:

// пакет repository
public interface UserRepository {
    User find(String id);
    void save(User user);
    void delete(String id);
}

public class PostgresUserRepository implements UserRepository {
    // ...
}

Это кажется естественным. Для меня это было по умолчанию. Репозиторий определяет контракт, реализация его выполняет, все довольны. Кроме потребителя, который теперь зависит от абстракции, которую не просил, включая delete, хотя ему нужен только find. Очень щедро. Очень бесполезно.

Go переворачивает это:

// пакет service
type UserFinder interface {
    Find(id string) (*User, error)
}

type UserStorage interface {
    Save(user *User) error
}

type UserService struct {
    finder  UserFinder
    storage UserStorage
}

Потребитель определяет ровно то, что ему нужно. Реализация просто удовлетворяет этим интерфейсам:

type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) Find(id string) (*User, error) {
    // ...
}

func (r *PostgresUserRepository) Save(user *User) error {
    // ...
}

Потребитель владеет интерфейсом, а не реализация. Не определяйте интерфейс для того, что вы предоставляете; определяйте его для того, что вам нужно. UserService не волнует, стоят ли за его зависимостями Postgres, Redis, файл или API. Он попросил Find и Save. Это и есть все отношения. Очень здорово, честно.

Go даёт вам свободу

Это одновременно и особенность, и бремя. Пугает, если вам нравится, когда говорят, что делать. Свобода, если вы хотите строить вдумчиво. Ловушка, если вы думали, что «без фреймворка» значит «без мышления».

Компромисс таков: фреймворки предотвращают плохие решения, ограничивая ваш выбор. Ну, не совсем; вы всё равно можете всё испортить. Ограничение в основном психологическое. Go делает вас ответственным за ваши решения, что менее утешительно, но более честно. Это значит, что ваша команда не может спрятаться за «фреймворк заставил нас». Вы не можете обвинить плохую архитектуру в соглашениях Rails. Если ваш проект на Go — бардак, это потому, что ваша команда так его сделала. Нет фреймворка, на который можно повесить вину. В этом вся фишка.

Чистая архитектура — это не фреймворк

Часто вижу заблуждение: кто-то читает пост о чистой архитектуре, копирует дерево папок в свой репозиторий и ждёт, когда чистота придёт. Она не приходит.

cmd/
internal/
  application/
  domain/
  infrastructure/
  entity/
  repository/
  usecase/
pkg/
tests/

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

cmd/main.go
internal/
  service.go
  postgres.go
pkg/models.go

Пока service.go не знает о Postgres, бизнес-логика не знает об HTTP, а конкретные реализации заменяемы. Вот и всё. Вы не получаете дополнительных очков архитектуры за папку с именем usecase.

Простота не означает отсутствие дисциплины

Go позволяет писать меньше шаблонного кода, но это не значит меньше дисциплины. Это значит, что дисциплина должна исходить от вас, что раздражает, потому что шаблонный код хотя бы ощущался как прогресс, я знаю.

  • Чёткие границы пакетов. Каждый пакет должен иметь одну, защищаемую цель. Импорт пакета должен иметь смысл: import "user/service" говорит о чём-то. import "user/pkg1/internal/common/helpers" говорит, что вы сдались и завели ящик для хлама.
  • Минимальные публичные API. В Go заглавная буква экспортирует. Подумайте, что вы экспортируете из каждого пакета. Если вы экспортируете всё, вы не делаете выбор, вы просто кричите.
  • Инверсия зависимостей там, где это уместно. Это не значит «используйте интерфейсы для всего». Преждевременная абстракция реальна. Но когда у вас есть внешние зависимости (база данных, API, файловая система), инвертируйте их. Позвольте бизнес-логике определять интерфейс, которому должна удовлетворять зависимость.
  • Маленькие интерфейсы. Лучшие интерфейсы в Go — это два или три метода, максимум. Интерфейс с десятью методами обычно является признаком того, что вы смешиваете обязанности или что вы портировали Java-интерфейс и надеялись, что никто не заметит.
  • Ревью кода, сосредоточенное на дизайне. Ваш стандартный чек-лист ревью, вероятно, содержит: «тесты?», «обработка ошибок?», «эффективность?». Добавьте: «Логичен ли этот поток зависимостей? Правильная ли это абстракция?» Дизайн так же важен, как и корректность. Его также легче упустить, потому что тесты всё ещё проходят, пока архитектура тихо умирает.

Заключение

Архитектура не живёт в языке программирования. Она живёт в решениях, которые принимают инженеры. Фреймворки могут обеспечивать согласованность. Они не могут обеспечить здравый смысл. Go просто даёт вам меньше ограждений и предполагает, что вы ими воспользуетесь. Иногда это окупается с лихвой. Иногда оставляет вас отлаживать бардак, когда вы должны спать. В любом случае, это на вас. Это не баг в языке. Это сделка.

Что делать прямо сейчас: возьмите свой текущий проект на Go и проверьте направление зависимостей. Убедитесь, что HTTP-хендлеры не знают о базе данных, сервисы не знают об HTTP, а интерфейсы определены там, где они потребляются. Начните с одного пакета и постепенно улучшайте остальные.

#Go#архитектура#зависимости#интерфейсы#чистая архитектура
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →