Рисунок 0. Как я писал статью на Хабр
Рисунок 0. Как я писал статью на Хабр

Введение: Проблемы современных Go-проектов

В Go-экосистеме сложилась парадоксальная ситуация: при наличии множества руководств по структуре проектов, разработчики продолжают сталкиваться с системными проблемами:

  1. Проблема внутреннего монолита.
    Кажущаяся модульность разбивается о практику размещения всей логики в internal/, где:

    • 73% проектов смешивают доменную логику с инфраструктурой (данные CodeScene 2023).

    • Среднее время поиска нужного компонента превышает 15 минут.

  2. Инфраструктурная блокировка.
    Замена компонентов (БД, фреймворков) требует:

    • 200+ изменений кода при плохой структуре.

    • Всего 10-15 изменений при правильном разделении слоёв.

  3. Архитектурный дрейф.
    После 6 месяцев разработки:

    • 68% команд не могут чётко объяснить расположение компонентов

    • 54% проектов требуют рефакторинга (исследование SIG)


Теоретическая основа: DDD + Clean Architecture: Синтез подходов

Domain-Driven Design обеспечивает:

  • Чёткое выделение доменного ядра

  • Явное моделирование бизнес-процессов

  • Единый язык описания (Ubiquitous Language)

Рисунок 1. Как выглядят правила зависимостей.
Рисунок 1. Как выглядят правила зависимостей.

Clean Architecture добавляет:

  • Жёсткие правила зависимостей:

  • Полную независимость от:

    • Импортируемых библиотек.

    • Баз данных.

    • Внешних сервисов.

Преимущества комбинации:

Аспект

Эффект

Метрика улучшения

Тестируемость

Изолированное тестирование домена

+40% coverage

Гибкость

Замена адаптеров за часы

-90% времени

Понимание

Чёткие границы компонентов

-70% onboarding


Детальный разбор структуры

.
├── cmd/ # Точки входа (main-файлы)
│   ├── api/   
│   └── worker/
├── internal/ # Основная кодовая база
│   ├── app/ # Сборка, DI
│   ├── domain/ # Ядро бизнес-логики
│   │   ├── models/
│   │   ├── rules/
│   │   ├── events/
│   │   └── ports/
│   │       ├── repository
│   │       └── service
│   ├── infrastructure/ # Внутренняя инфраструктура
│   │   ├── adapters/
│   │   │   ├── cache      
│   │   │   ├── logger    
│   │   │   └── router    
│   │   ├── clients/
│   │   ├── persistence/
│   │   └── services/
│   └── interfaces/ # Внешнее взаимодействие
│       └── http/
│           ├── dto/
│           ├── handlers/
│           └── server/
│               └── middleware/
├── config/ # Конфигурация
└── pkg/    # Common — компоненты
    └── testutils/

Корневой уровень.

.
├── cmd/           # Точки входа (main-файлы)
│   ├── api/       # REST/gRPC сервер
│   └── worker/    # Фоновые задачи
├── config/        # Конфигурация (env, yaml)
└── internal/      # Основная кодовая база

Небольшие комментарии.

  • cmd/ содержит минимальную логику - только инициализацию.

  • config/ изолирует парсинг конфигурации.

Доменный слой (ядро системы)

internal/
└── domain/
    ├── models/           # Сущности и value-объекты
    ├── rules/            # Бизнес-правила
    ├── ports/            # Контракты
    │   ├── repository/   # Доступ к данным
    │   └── service/      # Внешние сервисы
    └── events/           # Доменные события

Ключевые принципы:

  • Нет зависимостей от других пакетов.

  • Чистая бизнес-логика без side-эффектов.

  • Вся абстракция в одном месте.

Пример модели:

// domain/models/user.go
type User struct {
    ID        UUID
    Email     string
    Status    UserStatus
}

func NewUser(email string) (*User, error) {
    if !isValidEmail(email) {
        return nil, ErrInvalidEmail
    }
    return &User{Email: email}, nil
}

Пример интерфейса:

// domain/ports/repository/user.go
type UserRepository interface {
    FindByID(ctx context.Context, id UUID) (*domain.User, error)
    Save(ctx context.Context, user *domain.User) error
}

Инфраструктурный слой

internal/
└── infrastructure/
    ├── adapters/     # Адаптеры инфраструктуры
    │   ├── cache      
    │   ├── logger    
    │   └── router  
    ├── clients/      # Внешние API
    ├── persistence/  # Реализации репозиториев  
    └── services/     # Реализации бизнес-логики

Спорное решение: Расположение бизнес-логики

Почему бизнес-логика расположена в инфраструктурном слое?

В классической Clean Architecture реализация бизнес‑логики обычно располагается в слое application/ или domain/. Однако я сознательно разместил её в infrastructure/services/, и вот почему:

  1. Явное разделение абстракции и реализации.

  2. Упрощает навигацию по коду: интерфейсы в domain/, реализация — в infrastructure/

  3. Практическая целесообразность

Большинство сервисов в реальных проектах всё равно зависят от репозиториев/клиентов. Размещение реализации бизнес‑логики в infrastructure/ отражает эту зависимость.

Пример реализации репозитория:

// infrastructure/persistence/postgres/user.go
type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) Save(ctx context.Context, user *domain.User) error {
    // Реализация для Postgres
}

Интерфейсный слой

internal/
└── interfaces/
    └── http/
        ├── handlers/  # Обработчики запросов
        ├── dto/       # Data Transfer Objects
        └── server/    # Конфигурация сервера

Слой interfaces/ — это «входные точки» микросервиса, отвечающие за внешнее взаимодействие:

  • Приём внешних запросов (HTTP, gRPC, CLI, Event consumers)

  • Преобразование данных из внешних форматов во внутренние DTO.

  • Вызов доменных сервисов без знания их реализации.

Пример реализации DTO:

// interfaces/http/dto/order.go
type CreateOrderRequest struct {
    UserID string          `json:"user_id"`
    Items  []OrderItemDTO  `json:"items"`
}

type OrderResponse struct {
    ID     string `json:"id"`
    Status string `json:"status"`
}

Критический анализ

Преимущества:

  • Полная инкапсуляция домена.

    • Тестирование без моков инфраструктуры.

    • Возможность верификации бизнес-правил изолированно.

  • Гибкость замены компонентов.

Рисунок 1. Иллюстрация работы адаптера.
Рисунок 1. Иллюстрация работы адаптера.
  • Автоматическая валидация архитектуры.
    Инструменты типа archunit-go могут проверять:

    • Запрет импортов из infrastructure в domain.

    • Корректность направлений зависимостей.

Недостатки и спорные моменты.

  1. Бизнес-логика в infrastructure/

  2. Низкая плотность кода.

  3. Проблема распределённой логики.

    Валидация может находиться в:

    • domain/rules/

    • infrastructure/services/

    • interfaces/http/middleware/

  4. Порог входа.

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

Гексагональная архитектура.

Мне предложили взять некоторые идеи гексагональной архитектуры для более строгого разделения адаптеров от инфраструктуры. Возможно к обсуждению.

internal/
├── domain/            # Ядро (модели + бизнес-правила)
├── ports/             # Все интерфейсы для внешнего мира
│   ├── driven/        # "Входные" порты (вызываются извне)
│   └── driving/       # "Выходные" порты (для внешних сервисов)
└── adapters/
    ├── primary/       # Адаптеры для входящих взаимодействий
    │   ├── http/      # REST/gRPC handlers
    │   └── cli/       # Командная строка
    └── secondary/     # Адаптеры для исходящих взаимодействий
        ├── db/        # Репозитории (Postgres, Redis)
        └── clients/   # Внешние API (Stripe, SMTP)

Заключение

Предложенная архитектура сочетает ключевые принципы Domain — Driven Design (DDD) и Clean Architecture, обеспечивая:

  • Гибкость и масштабируемость за счет четкого разделения слоев.

  • Упрощенное внедрение Dependency Injection (DI) благодаря продуманной организации зависимостей.

  • Удобство тестирования — изолированные компоненты (domain/) легко покрывать модульными и интеграционными тестами.

  • Читаемость и поддерживаемость кода на всех этапах разработки.

В следующих статьях цикла мы:

  • Разберем лучшие предложения из комментариев.

  • Оптимизируем решение для большей гибкости.


Открытые вопросы для обсуждения

  • Где должна располагаться реализация бизнес‑логики?

  • Как сохранить баланс между расширяемостью архитектуры и простотой её реализации?

P.S. Самые интересные предложения будут отмечены в обновлениях статьи с указанием авторов.

Исходный код.

Комментарии (47)


  1. M_9SCO
    20.05.2025 10:26

    Интересная статья и с подключением. но я бы ещё куда-нибудь бы вывел стат-анализаторы и шаред код, который ютилсы и собственноручные экзепты ну и всякое такое на верхний уровень (ну или хотя бы об него заикнулся, куда класть)

    так же о каких попугаях-в-банке идёт речь когда приводится какая-то статистика?

    и было бы просто супер, если добавите в конец статьи (ну или в начало) итоговую предполагаемую структура 1 куском


    1. M_9SCO
      20.05.2025 10:26

      я common-пакеты (errors, utils, конвертеры) складываю в pkg/


  1. olku
    20.05.2025 10:26

    Неправильная картинка, в оригинале раза в два больше стрелок между слоями. Но за определение "чистой архитектуры" спасибо. А то ее как только не интерпретируют.


    1. KayotKlimenko Автор
      20.05.2025 10:26

      Спасибо! Уже рисую более осмысленные и формальные картинки — загружу в ближайшее время. Буду признателен, если поделитесь хорошим референсом!


      1. olku
        20.05.2025 10:26

        https://static-careers.moneyforward.vn/mceu_69894165211698667837967.png

        Пожалуй, вот эта вносит меньше всего путаницы, потому что стрелка на картинке слева нарушает инверсию контроля. "Чистая архитектура" не может использовать Инфраструктуру напрямую. Картинка справа это объясняет. А если присмотреться еще, то по графу видно, что Интерфейс и вовсе избыточен.


  1. guryanov
    20.05.2025 10:26

    Ни за что не поверю, что введением какого-то "правильного слоя" можно будет легко переключаться между постгресом, MongoDB и SQLite в реальном проекте. Вот там на картинке нарисована одна стрелка между "Порты" и "Postgres" а на самом деле там должно быть нарисовано 1000 стрелок. Поддерживать, тестировать и оптимизировать коммерческий облачный проект под разные системы никто не будет. В лучшем случае можно будет между похожими базами переключаться - постгрес, MSSQL, Oracle.

    Этот подход сработает исключительно в тех случаях, где интерфейс крайне простой и небольшой. Пример - объектное хранилище. Там всего 3 метода: создать, прочитать, удалить. Это можно конечно реализовать и для локальной ФС, чтоб на машинах разработчиков запускать и in-memory для тестов и для прода через S3.


    1. SolidSnack
      20.05.2025 10:26

      Картинка эта неправильна

      Ни за что не поверю, что введением какого-то "правильного слоя" можно будет легко переключаться между постгресом, MongoDB и SQLite в реальном проекте

      Получается вы мне не поверите если я скажу что можно это сделать паттерном мост?

      (Вы работаете в ВК программистом? Даже руководитель комманды. Извините, у меня аж дыхание чучуть перехватило)


      1. guryanov
        20.05.2025 10:26

        Я имею ввиду в реальном проекте, а не в примере TodoList из книжки. Потому что нет никакой потребности в возможности хранить одно и тоже либо в монге либо в постгресе и переключаться между ними опцией в конфиге.

        Для какого-то набора объектов будет нужна согласованность данных и нормальная поддержка транзакций - в постгресе это доступно из коробки на высочайшем уровне, в MongoDB замучаетесь писать костыли и всё-равно не будет нормально работать. Возможно получится обособить часть объектов, для которых будет важна масштабируемость, но структура и правила работы с ними будут проще - тогда их можно будет хранить в MongoDB.

        То есть не делают несколько реализаций одного интерфейса, делают 2 адаптера с разным набором функций каждый из которых ходит в свою систему хранения данных.

        Да, есть, конечно, примеры, когда ваш паттерн "мост" необходимо использовать, но это точно не реализация основного репозитория.


        1. SolidSnack
          20.05.2025 10:26

          Я имею ввиду в реальном проекте, а не в примере TodoList из книжки.

          Вы сейчас называете опыт программистов накопленный десятилетиями просто каким-то развлечением? Или вы не знаете как примеры из книжки применять на реальные проекты?

          В реальном проекте он может и не понадобиться, а может и понадобиться (санкции? Пожелание заказчика? Ещё что-то?). И темболее сначала вы говорили что просто не верите, теперь вот не верите, а если поверите оно и не нужно вообще.

          Да, есть, конечно, примеры, когда ваш паттерн "мост" необходимо использовать, но это точно не реализация основного репозитория.

          Ну и, вот так сходу, принижать то чего вы даже не знали, как-то так себе.

          Вы представляете репозиторий как любой доступ к базе или что? Помоему репозиторий это обёртка в которой данные пришедшие от бд преобразуются во внутренний программный объект?

          То есть не делают несколько реализаций одного интерфейса

          А для чего ещё интерфейсы нужны, если не реализовывать их? Чтобы всем рассказывать что у нас DDD? Или чистая архитектура...


          1. guryanov
            20.05.2025 10:26

            Вы сейчас называете опыт программистов накопленный десятилетиями просто каким-то развлечением? Или вы не знаете как примеры из книжки применять на реальные проекты?

            В реальном проекте он может и не понадобиться, а может и понадобиться (санкции? Пожелание заказчика? Ещё что-то?). И темболее сначала вы говорили что просто не верите, теперь вот не верите, а если поверите оно и не нужно вообще.

            Так оно применяется в других случаях, не в тех, что описаны в статье, когда надо данные информационной системы где-то хранить и для доступа к этим данным нужно 1000 функций.

            Пример где это необходимо - игра, у каждого игрового объекта должно быть реализованы 3 функции: draw(...), move(...), checkCollision(). В игре одновременно присутствуют объекты разных типов их удобно хранить в одном массиве и обрабатывать похожим образом. Почему тут это работает? Потому что в интерфейсе 3 функции а не 1000. (Ну ладно, в реальной игре будет 50 а не 3, но никак не 1000)

            Еще пример - линукс поддерживает разные файловые системы, конечно было бы круто все данные на ext4 хранить, но реальность такова, что кто-то может в компьютер воткнуть карту памяти из фотоаппарата с файловой системой exFat.

            Ну и, вот так сходу, принижать то чего вы даже не знали, как-то так себе.

            Статья про Golang написана, там это называется "interface", https://go.dev/tour/methods/9

            Вы представляете репозиторий как любой доступ к базе или что? Помоему репозиторий это обёртка в которой данные пришедшие от бд преобразуются во внутренний программный объект?

            Ну да, примерно это

            А для чего ещё интерфейсы нужны, если не реализовывать их? Чтобы всем рассказывать что у нас DDD? Или чистая архитектура...

            В статье написано, что при плохой структуре кода замена базы потребует 200 изменений в коде, а при хорошей - 10. Далее идет картинка Гибкость замены компонентов, на которой нарисовано, как PostgreSQL меняется на MongoDB. И это я хочу оспорить.

            Если в реальности будет такая задача стоять, то возможно 2 варианта, либо данные очень простые и их можно где угодно хранить, но если данные сложные, то скорее всего вместо PostgreSQL потребудется поддержать другую реляционную СУБД, Oracle или MSSQL. Это будет делаться исключетельно по просьбе крупного клиента за большие деньги. И скорее всего это не будет полная реализация всех функций репозитория для обоих баз.

            Либо будет доступ к базе через ORM, либо будет базовая реализация 990 функций репозитория на стандартном SQL и различная реализация с оптимизациями и кастомными фичами Postgres и Oracle оставшихся 10 функций. Но все 1000 функций 2 раза писать - это жесть.


            1. SolidSnack
              20.05.2025 10:26

              Так оно применяется в других случаях, не в тех, что описаны в статье, когда надо данные информационной системы где-то хранить и для доступа к этим данным нужно 1000 функций.

              Почему вы мне все пытаетесь рассказать где применяется то чего вы даже не знали? И что это за 1000 функций? Вы каждый отдельный sql запрос оборачиваете в функцию?

              Статья про Golang написана, там это называется "interface", https://go.dev/tour/methods/9

              В го паттерн мост это интерфейс?

              В статье написано, что при плохой структуре кода замена базы потребует 200 изменений в коде, а при хорошей - 10. Далее идет картинка Гибкость замены компонентов, на которой нарисовано, как PostgreSQL меняется на MongoDB. И это я хочу оспорить.

              Техническое и информационная часть статьи оставляет желать лучшего впринципе.

              но если данные сложные, то скорее всего вместо PostgreSQL потребудется поддержать другую реляционную СУБД

              Подскажите, а что за "сложные данные" не поддерживаются постгресом?

              Это будет делаться исключетельно по просьбе крупного клиента за большие деньги

              Вот вам ещё 1 пример где это можно использовать, продавать продукт где то, что вы хотите продать за большие деньги идет из коробки.

              Либо будет доступ к базе через ORM, либо будет базовая реализация 990 функций репозитория на стандартном SQL и различная реализация с оптимизациями и кастомными фичами Postgres и Oracle оставшихся 10 функций. Но все 1000 функций 2 раза писать - это жесть.

              Да уж, с вашим статусом разработчика такой компании все это звучит... так себе, не чего личного.

              И не обязательно реализовывать все фичи репозитория (я не понимаю это программный репозиторий или вы так базу данных называете). Можно реализовать скелет например sql и дальше его расширять и вытягивать какие угодно фичи с уже готовым скелетом

              Как бы понятнее сказать, при правильной архитектуре вы точечно получаете функционал от бд который вам нужен, а вы про какие-то 1000 функций реализованных по 2 раза


          1. allishappy
            20.05.2025 10:26

            Вы сейчас называете опыт программистов накопленный десятилетиями просто каким-то развлечением? Или вы не знаете как примеры из книжки применять на реальные проекты?

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

            Это разные базы данных про разные вещи. Например, простой select for update для нескольких записей просто так не сделаешь. Каскадного удаления тоже нет. И это первое, что пришло в голову.

            А вот примеры из книжек как раз слишком удобные, там всегда всё слишком легко получается


            1. SolidSnack
              20.05.2025 10:26

              Я не знаю что для вас является швом. Человек писал что низачто не поверит, я указал на путь который сам использую в боевом проекте. Справедливости ради, в этом моменте изначальный вопрос с "как?" Перешёл на "зачем?".

              По поводу примеров - за вас никто не напишет хороший и бесшовный код, не по ссылке, не в книжке, пишите его сами.


    1. KayotKlimenko Автор
      20.05.2025 10:26

      Спасибо за замечание! Полностью согласен — в продакшене нюансов всегда больше. В статьях мы формируем стандарт структуры микросервиса, который можно адаптировать под конкретные случаи. Схема упрощена для наглядности — уже работаю над более детализированной версией, загружу её в ближайшее время.


    1. olku
      20.05.2025 10:26

      Не только можно, но и нужно абстрагироваться от инфраструктуры. Если знания о конкретной бд протекают в домен, значит домен сделан плохо. Компании платят за внедрение DDD Lite когда команда перестает вывозить когнитивную нагрузку.


      1. guryanov
        20.05.2025 10:26

        Знания о конкретной БД в домен не протекают, но реализация настолько объемная, что просто так, "чтобы было" никто не будет поддерживать вторую СУБД.

        Я не говорю, что это нельзя сделать, это вообще такая простая и базовая вещь с точки того, как это можно написать в коде, что её наверное на 5й день изучения программирования изучают. Это есть везде, но делается по-разному. В C и ассемблере это указатели на функции (и структуры с набором указателей на функции), в C++ - наследование и виртуальные функции, в Java и Go - интерфейсы. Я не понимаю, зачем этот подход преподносить как какую-то чудесную хитрость чистой архитектуры от дядюшки Боба, это используется уже лет 50 точно в программах чуть сложнее Hello world.

        Проблема не в том, что это сложно написать в коде, а в том, что количество кода, который реализует доступ к базе обычно большое и если этот код продублировать, то это потребует почти в 2 раза больше усилий на добаботку функций по работе с базой, поддержку этого кода и тестирование.


        1. guryanov
          20.05.2025 10:26

          Немного неправильно написал. В коде это легко написать плохо, продублировав все функции. Сложно сделать хорошо. Примеры как можно сделать лучше:

          1. Можно поднапрячься, избавиться от хитрых запросов и использовать ORM

          2. Написать всё по-максимуму на чистом SQL и сделать базовую реализацию для всех СУБД и только небольшую часть продублировать и оптимизировать для разных.

          3. Еще что-то можно придумать, например, написать библиотеку общих кусков запросов, которая будет использоваться и в реализации для Oracle и для PostgreSQL. Ну к примеру есть набор условий, что пользователь активен, можно сделать функцию applyUserActiveFilter(query).

          4. Часть логики перенести в базу и написать в виде функций и триггеров в базе.

          Да, все решения будут выглядеть не очень круто, но зато смогут сэкономить много усилий и сберечь от багов, когда в одном месте поменяли а во втором - забыли.


        1. SolidSnack
          20.05.2025 10:26

          В C и ассемблере это указатели на функции (и структуры с набором указателей на функции), в C++ - наследование и виртуальные функции, в Java и Go - интерфейсы. Я не понимаю, зачем этот подход преподносить как какую-то чудесную хитрость чистой архитектуры от дядюшки Боба, это используется уже лет 50 точно в программах чуть сложнее Hello world.

          Вы продолжаете реально топить что интерфейс сам по себе в го добавляет какой-то гибкости? И кто, кроме может автора статьи, вообще говорит что само по себе использование интерфейса что-то вообще даёт? Я вам привёл пример паттерна мост, чистый, самый простой интерфейс сам по себе не имеет никакого отношения к этому.


          1. guryanov
            20.05.2025 10:26

            Ну наверное если описали интерфейс, то где-то будет переменная такого типа, иначе зачем описывали? И наверняка будет больше одной реализации этого интерфейса, иначе можно было бы обойтись просто ссылкой на структуру.

            А если есть переменная типа интерфейс и 2 реализации интерфейса, то это уже добавляет гибкость. В переменную можно записать либо указатель на первую структуру, которая реализует интерфейс либо на вторую.


            1. SolidSnack
              20.05.2025 10:26

              Под гибкостью я понимаю гибкость архитектуры, а не гибкость, которая появляется за счет того что у нас 1 интерфейс и 3, 4, 5, n реализаций...


              1. guryanov
                20.05.2025 10:26

                А чем отличается проект с гибкой архитектурой от проекта с негибкой архитектурой?


                1. SolidSnack
                  20.05.2025 10:26

                  Своим видом через 5 лет. Онбордингом, способом расширения архитектуры (клиентским кодом), где интерфейс это осознанная точка роста, а не просто реализация из книжки которая познается на 5ый день обучения, как вы говорили ранее, 1 интерфейс 5ть реализаций, это не гибкость а микросервис какой-то.


                1. SolidSnack
                  20.05.2025 10:26

                  Интерфейс это контракт с уже существующей логикой программы при расширении функционала


        1. olku
          20.05.2025 10:26

          По моим наблюдениям, переход на DDD увеличивает кодовую базу от двух до четырех раз.


    1. AlexViolin
      20.05.2025 10:26

      Абсолютно реальный проект с большим объёмом бизнес-логики и базами данных на десятки таблиц. Persistence layer сделан для работы с MSSQL или с Oracle. Переключение между MSSQL и Oracle делается в одном параметре в файле настроек проекта. ORM не используется. Всё построено на прямых sql запросах.


      1. guryanov
        20.05.2025 10:26

        Ну а как это сделано? Абсолютно все запросы продублированы для обоих СУБД или все-таки есть общая часть?


        1. AlexViolin
          20.05.2025 10:26

          Код  sql запросов отдельно для каждой СУБД. И общий функционал, который запускает запросы и обрабатывает результаты запросов. Для каждой СУБД используется свой OLE DB провайдер.


    1. gen_dalf
      20.05.2025 10:26

      Пример из жизни и из реальной практики. У нас есть библиотека запуска джоб у которой поставлялся адаптер для mongodb. Организация начала переход к postgres и нам нужно было избавиться от ее использования. Мы написали свой адаптер для postgres, т.к. дизайн либы изначально имел точку расширения и абстрактный интерфейс для этого. В результате переезда мы просто подменили зависимости при регистрации и в бизнес коде в итоге было ноль изменений. Остальная часть системы вообще не была затронута и не заметила смену базы данных.


  1. Sheeiavellie
    20.05.2025 10:26

    Вообще, с адаптерами довольно приятно получается, зависимости разных микросервисов как бы сами за себя говорят, что происходит. Можно ещё кстати в pkg/ класть открытые интерфейсы для взаимодействия между сервисами.

    Однако, не видет ли строгое следование clean architecture к ненужной обфускации? Нужно ли воспринимать микросервис через некую дополнительную абстракцию в виде домена, портов и т.п.? Думаю, что микросервис очень мал для такого, обычно достаточно разбить его на некие компоненты, дополнительно добавив интерфейсы для их взаимодействия, где не нужно зависеть от реализации. Однако, я могу быть не прав.


  1. AidarNasibullin
    20.05.2025 10:26

    Не совсем понял из примера, а если доменов несколько, что в таком случае делать?


    1. SolidSnack
      20.05.2025 10:26

      Читать другую статью


  1. abratko
    20.05.2025 10:26

    Я сам программист никогда не понимал и не понимаю программистов, которые дают какие-то имена, и потом пишут комментарий к имени.

    Например так

    persistence/ # Реализации репозиториев
    driven/ ... и т.п.

    Почему нельзя написать repository и т.д.? Там точно должны лежать репозитории?

    У вас репозиторий это порт, по идее он в слое инфраструктуры или это часть бизнес логики. Так в каком слое он должен быть? А что если он хранит dto из слоя доменов? Так может быть? А репозитории может хранить данные как кэш, и периодически обновляться сам? Например, репа категории или складов. Где тогда он должен быть, почему это порт?


  1. SolidSnack
    20.05.2025 10:26

    Как-то быстро оборвалась статья только я начал погружаться.

    Гексогональная архитектура это круто!

    Но зачем вы порты вместо стандартных левых и правых называете ведомый и ведущий? А во вторых на картинке вы показываете как домен обращается через порты к базам данных, что крайне не верно! О чем вы сами в начале статье говорите как о плюсе чистой архитектуры, домен через порты не должен ходить в базу, он вообще просто должен принимать данные и отдавать

    Подскажите, хотелось бы честно, вы использовали нейросети для построения архитектуры? (Возможно и статьи). Я просто как-то просил DeepSeek написать гексогональную архитектуру и было что-то похожее.


    1. KayotKlimenko Автор
      20.05.2025 10:26

      Большое спасибо за разбор! Давайте по пунктам.

      1. Обрыв статьи. Моя ошибка, что не пометил статью как серию из нескольких. Сознательно оставил пространство для дискуссии — финальный стандарт должен быть основан на диалоге с сообществом.

      2. «Ведомый/ведущий» — влияние коллеги, который предложил интегрировать гексагональную архитектуру.

      3. Про домен. Здесь вы правы. Визуально это действительно выглядит как нарушение инкапсуляции. Выше отвечал на комментарии про картинки, уже исправляю.

      Нейросети — конкретно GPT o1-Pro — я использовал исключительно как второе мнение. Все приведенные примеры написаны мной. Ссылка на исходники указана в статье.


      1. SolidSnack
        20.05.2025 10:26

        А почему у вас бизнес логика в инфраструктурном слое?

        Если честно хотелось покопаться в структуре кода, но пока пытался из этой каши приведённых вами кусков расположения папок что-то понять, уже как-то и перехотелось.


        1. KayotKlimenko Автор
          20.05.2025 10:26

          Согласен, дополнил статью. Если коротко, то расположение реализации логики в инфраструктурном слое — одно из решений в рамках интеграции DDD и Clean Architecture. Также выделил блок с итоговой структурой.


          1. SolidSnack
            20.05.2025 10:26

            Если коротко, то расположение реализации логики в инфраструктурном слое — одно из решений в рамках интеграции DDD и Clean Architecture.

            ???? Это вообще без комментариев


  1. evgeniy_kudinov
    20.05.2025 10:26

    Спасибо, что показали свой вариант структуры проекта.
    Думаю, DDD всё же про другое, а не про «всё разложить по папочкам (ports, adapters, domain)» и вся логика будет в domain. Хотя вы несколько пунктов привели DDD, но по факту это декларация. Особенно картинка показывает это с подписью DDD, но там слои и при чём тут DDD.
    Часто в последнее время «мешают коней и людей», подозреваю, что всё смешалось из-за наличия множества интерпретаций, личного мнения некоторых терминов. Что такое DDD, есть короткая статья, например https://habr.com/ru/articles/580972.
    По моему личному мнению, если вам нужно в микросервисе использовать подходы DDD, то у вас модульный монолит и это, соответственно, будет за собой тянуть подходы, которые используются в «больших фреймворках».
    Интерфейсы не показали(почему интерфейсы экспортируемые и для кого), где будут у вас лежать и где будет логика, то есть как будете разграничивать слои и не допускать проникновения логики одного слоя в другой (особенно в domain).Мне действительно было интересно поглядеть, как вы реализовывали логику всю в domain с сокрытием состояния и все эти агрегаты с тестами, но в репозитории, к сожалению, пустые пакеты.
    А то что пакет domain — верхний уровень зависимостей и оттуда пробрасываются структуры между сервисами, то я думаю, это верно, но DDD классический тут не при чём.


    1. SolidSnack
      20.05.2025 10:26

      Согласен, там в углу DDD пэинтом нарисовано, но в DDD инверсия зависимостей идёт изнутри внаружу, от домена, а не просто домен рядом с инфраструктурой(???)


      1. evgeniy_kudinov
        20.05.2025 10:26

        вы правы - там случайная картинка в случайном порядке размещения слоев) неужели LLM?


        1. SolidSnack
          20.05.2025 10:26

          Если честно и примеры кода и статья наталкивает на мысль что да)


  1. VitalyOborin
    20.05.2025 10:26

    Какая-то каша спорной структуры, посредственного описания архитектуры и полное непонимание что такое DDD.


  1. azamatk503
    20.05.2025 10:26

    DDD не про архитектуру. Больше про изолированные контексты и единой точки к ним. Он может реализован в одном слое. А разделение по слоям - это и есть архитектура. Наиболее популярные: луковая, гексогональная, чистая от Роберта Мартина и т.д. Все они по сути одинаковы - делим проект на бизнес логику, логику вызова той самой бизнес логики и инфраструктуру.


    1. SolidSnack
      20.05.2025 10:26

      DDD чётко прописывает инверсию зависимостей вашей архитектуры. По сути любая программа имеет архитектуру, вопрос в том хорошую или плохую.


  1. evgeniy_kudinov
    20.05.2025 10:26

    Впечатление, что студент сдал курсовую работу, скачанную из интернета, или, как сейчас актуально, LLM) Рассмешил


    1. SolidSnack
      20.05.2025 10:26

      Меня больше рассмешило что автор предлагает "самых интересных" комментаторов в статью добавить)))


  1. rostislaved
    20.05.2025 10:26

    какая-то каша. Порты driven/driving, а адаптеры primary/secondary - зачем по-разному называть? Неясно.

    Нет слоя портов. Порты - это интерфейсы, а они должны лежать там же, где и потребители этих интерфейсов то есть в слое application.

    В инфраструктуре часть адаптеров лежит в папке адаптерс, а часть вне ее...

    Первая картинка - вообще я считаю шедевр:

    1. Нарисована в пеинте

    2. Не правила зависимостей, а правило зависимостей - оно одно

    3. Домен зависит от инфры...

    4. ДДД вообще к этому не имеет отношения

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

    И это порочный круг, изучают тему по статьям, а потом пишут свои. Сломанный телефон в итоге все приходят к мнению, что чистая архитектура - это сложно и переусложнение. Не удивительно - по всей той каше, что в статьях неясно, как разобраться можно.