В этой части мы обсудим структуру шаблона файлов и папок для достижения чистой и НАДЕЖНОЙ архитектуры.
Возможно, вы слышали об этом блоге 2012 года: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html.
Если я могу упростить пункты, вот требования для достижения чистой архитектуры:
- Независимость от фреймворков. Архитектура не зависит от библиотек или функций.
- Тестируемый. Бизнес-правила можно протестировать без пользовательского интерфейса, базы данных, веб-сервера или любого другого внешнего элемента.
- Независимость от пользовательского интерфейса.
- Независимость от базы данных. Вы можете заменить SQL Server на Mongo.
- Независимость от каких-либо внешних агентств.
Но для меня это все еще было абстрактно. Пока не увидел диаграмму в этом посте: 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
Спасибо за чтение. Привет 🍻