В этой части мы обсудим структуру шаблона файлов и папок для достижения чистой и НАДЕЖНОЙ архитектуры.

Возможно, вы слышали об этом блоге 2012 года: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html.

Если я могу упростить пункты, вот требования для достижения чистой архитектуры:

  1. Независимость от фреймворков. Архитектура не зависит от библиотек или функций.
  2. Тестируемый. Бизнес-правила можно протестировать без пользовательского интерфейса, базы данных, веб-сервера или любого другого внешнего элемента.
  3. Независимость от пользовательского интерфейса.
  4. Независимость от базы данных. Вы можете заменить SQL Server на Mongo.
  5. Независимость от каких-либо внешних агентств.

Но для меня это все еще было абстрактно. Пока не увидел диаграмму в этом посте: https://github.com/bxcodec/go-clean-arch

Теперь это многое проясняет. По сути, это говорит о том, что если Сервис хочет общаться с Контроллером или хочет общаться с Репозиторием, это может быть не напрямую с реализацией, а через интерфейс. Поэтому изменения контроллера или хранилища не обязательно должны сопровождаться изменением реализации бизнес-логики. Каким-то образом мы можем связать это с принципом SOLID, по крайней мере, для меня буквы S, L и D тесно связаны с требованиями чистой архитектуры. Когда мы поместим интерфейс на картинку, это может выглядеть примерно так:

Но подождите, домен/модель не нуждается в интерфейсе?

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

Одна вещь, с которой я, вероятно, не совсем согласен с диаграммой, касается микросервисов. Для меня это не подходит для репозитория, но должно быть в контроллере/доставке. Потому что, когда мы говорим о межмашинном взаимодействии в Go, обычно они осуществляются в форме gRPC, REST API или через Twirp RPC. Я понимаю, что идея автора для контроллера в основном связана с входящим запросом. Но чтобы шаблон был последовательным, можно создать новую категорию «исходящие» или «внешние» под контроллером. Подробнее об этом примере в другой части руководства.

Структура папок

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

Для этого урока (и следующих частей) я размещаю репозиторий github здесь https://github.com/hariesef/myschool.

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

Вернуться к основной теме. В лучших практиках Go в основном определены только три папки:

Папка: cmd

Здесь находится основной пакет. Тот, который мы должны скомпилировать и встроить в двоичный файл. Если мы собираемся доставить несколько двоичных файлов, то есть один для сервера REST API, один для клиентского теста, другой, например, для CLI, то структура папок может выглядеть так:

В этом руководстве мы реализуем только cmd/server/main.go.

Папка: внутренняя

Сюда мы помещаем наши внутренние пакеты. Просто проверьте эти условия:

  • Мы не хотим делиться пакетами с другими проектами
  • Мы не можем поделиться, потому что это зависит от других внутренних пакетов
  • Подпакет нельзя скопировать и вставить, и ожидается, что он запустит PnP в другом проекте.

Папка: pkg

ХОРОШО. Если у нас есть папка с именем internal, наверняка у нас есть и противоположная папка с именем external, верно? Да, но, к сожалению, соглашение для доступных извне пакетов в Go называется pkg. Прежде чем положить что-то в папку pkg, рассмотрим следующие условия:

  • Можно использовать в других проектах
  • Не иметь зависимости от внутренних пакетов
  • Можно скопировать и вставить и ожидать запуска PnP

Предложение подпапки

Опишем по порядку, что может быть под папками:

  • модель: содержит файлы интерфейса, которые представляют, как мы храним данные в нашем хранилище. Обычно каждая вложенная папка может иметь связь 1-1 с таблицей в базе данных. При более сложных требованиях может быть добавлен отдельный интерфейс (впоследствии он будет реализован как репозиторий), который связывает несколько таблиц, чтобы выполнять более сложные запросы, такие как объединение таблиц.
  • макеты: здесь из интерфейсов генерируются фиктивные файлы. В этом уроке я буду использовать gomock. (В качестве альтернативы есть также Mocker и Testify). Большинство интерфейсов обычно необходимы как часть конкретной реализации бизнес-логики/сервисов, поэтому они являются внутренними. Но если вы создаете импортируемую библиотеку, а также предоставляете макеты пользователю, эта папка также может находиться в pkg.
  • контроллер: сюда мы помещаем интерфейсы и реализации, такие как RPC, REST API, CLI и т. д.
  • репозитории: эта папка предназначена только для установки и оболочки структуры для хранилищ. Мы не ожидаем сложных файлов или подпапок внутри.
  • хранилище:в подпапке мы можем определить другой тип хранилища. В этом уроке мы будем использовать sqlite и mongodb.
  • службы: здесь мы изобретем новую службу, которая будет содержать все наши конкретные варианты использования бизнес-логики. Обычно службы взаимодействуют как с контроллером, так и с репозиторием.
  • helper: наконец, здесь могут находиться любые независимые библиотеки, которые могут широко и просто использоваться другими пакетами. Некоторые примеры: функция шифрования или функции для получения и анализа конфигураций из переменных среды.

Это все для части 1, перейдите к следующей части руководства: https://hariesef.medium.com/go-e2e-tutorial-part-2-models-and-implementation-via-gorm-19ac6f9104e6

Спасибо за чтение. Привет 🍻