Найти в Дзене
Golang-news

Как создать качественный бэкенд Golang

Создание проекта Golang с нуля требует принятия некоторых важных решений, которые будут определять будущее проекта, предоставляя вам и будущим членам команды основу для масштабирования кодовой базы. При присоединении к существующему проекту большинство этих решений уже приняты, и отдельным участникам легко придерживаться статус-кво. Знание того, как создать приложение с нуля, не только даст вам больше свободы для создания собственных приложений, но и поможет вам лучше понять, почему ваша существующая кодовая база построена именно так, как она есть. В этом сообщении в блоге будут рассмотрены шаги по начальной загрузке нового бэкэнда Golang: В этом посте также будут обсуждаться некоторые не столь очевидные решения, которые необходимо сделать на этом пути, а также некоторые мысли моих коллег из 8-го Света по поводу интересного вопроса, связанного с тестами. Этот пост предполагает, что вы GO правильно установили и готовы к использованию. Без лишних слов, давайте приступим к делу! ВЫБОР HTT
Оглавление

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

В этом сообщении в блоге будут рассмотрены шаги по начальной загрузке нового бэкэнда Golang:

  • Выбор HTTP-фреймворка
  • Проектирование масштабируемой структуры папок
  • Создание хороших практик для набора тестов

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

Этот пост предполагает, что вы GO правильно установили и готовы к использованию. Без лишних слов, давайте приступим к делу!

ВЫБОР HTTP-ФРЕЙМВОРКА

Для проектов Golang доступно множество HTTP-фреймворков, включая Echo , Iris , Beego и Gin . При выборе фреймворка разработчики учитывают такие факторы, как скорость выполнения, ясность, популярность, обслуживание и собственный опыт разработчика. Популярность и поддержка идут рука об руку: чем она популярнее, тем больше людей заинтересованы в ее активной поддержке. При выборе зависимости с открытым исходным кодом лучше выбрать ту, которая при необходимости будет исправлять ошибки или решать недавно возникшие проблемы безопасности. Опыт разработчика также должен быть принят во внимание, чтобы максимально использовать ваши ресурсы, хотя лучше не основывать решения исключительно на нем.

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

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

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

В этом примере в качестве основы будет использоваться Gin-Gonic из-за его огромного сообщества, скорости и того факта, что у него все еще есть выпуски. Теперь мы просто получаем это:

go get github.com/gin-gonic/gin

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

ПРОЕКТИРОВАНИЕ МАСШТАБИРУЕМОЙ СТРУКТУРЫ ПАПОК

Golang рекомендует использовать плоскую архитектуру. Максимально плоско. Это означает, что вы можете начать добавлять .goфайлы прямо в корневую папку. Однако на 8th Light мы увидели, как размещение файлов и папок Go в корневом каталоге может усложнить дальнейшую разработку. Ознакомьтесь с golang-standards/project-layout, чтобы узнать, как организовать большие проекты Go.

Все эти проблемы решаются с помощью следующей структуры. Представьте себе приложение с двумя разными HTTP-ресурсами users и pings:

структура проекта на GO
структура проекта на GO

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

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

Если рассматривать /app папку как папку, содержащую исключительно связь между бизнес-логикой и HTTP, то некоторые вещи должны оставаться отдельными. Папка /config может позаботиться о таких вещах, как чтение переменных среды или анализ .yaml файла конфигурации. Папка конфигурации отделена от основного приложения, так как она не должна знать о каких-либо переменных среды или конкретной конфигурации. В этом отношении папка /db аналогична: ее можно реализовать с помощью любого другого внешнего инструмента, а миграции и схема не должны быть частью приложения.

Но как работает архитектура портов и адаптеров и как она выглядит в этой структуре папок? Начнем с файла app/users/router.go:

-2

Функция Routesдобавляет путь /users/:id к полученному engine. Эта функция позволяет вам использовать композицию и создавать все маршруты в вашем приложении или просто создавать маршруты пользователей при тестировании пакета users.

Добавляя аналогичный файл в app/pings/router.go, мы можем иметь:

-3

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

Все это собрано в app/launcher.go файле:

-4

Реализация контроллеров пока не важна. Вы можете использовать шаблон построения файла launcher.go для создания тестов для ваших маршрутов и контроллеров.

УСТАНОВЛЕНИЕ ХОРОШИХ ПРАКТИК ДЛЯ НАБОРА ТЕСТОВ

Go поставляется со встроенной минимальной средой тестирования. Хотя он отлично работает, его можно расширить с помощью сред BDD, таких как Goblin и Ginkgo , и/или с помощью библиотек утверждений, таких как Gomega и stretch/testify .

Компромисс, связанный с использованием инфраструктур BDD, заключается в том, что распараллеливание тестов становится менее очевидным и управляемым: хук BeforeEach является общим для всех модульных тестов, поэтому могут возникнуть конфликты при распараллеливании между модульными тестами, либо это так, либо ваш набор тестов в конечном итоге будет полностью последовательно. Это никому не нравится. Если бы нам пришлось выбирать одну структуру BDD, мы бы выбрали комбинацию Ginkgo + Gomega, поскольку она легко читается и поддерживает генераторы данных, макеты и группировку.

В этом блоге мы используем только stretch/testify библиотеку утверждений, так как она гораздо популярнее, чем Gomega одна (по состоянию на май 2022 года):

go get github.com/stretchr/testify

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

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

Еще одна спорная тема — тестировать ли частные методы. В Go это означает создание тестов внутри того же пакета, в котором определен код, или в другом _test пакете:

-5

Вот как выглядит тестирование как экспортированный метод снаружи:

-6

И как это выглядит протестировано изнутри:

-7

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

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

Я спросил некоторых своих коллег из 8th Light об их мнении на эту тему. Вот что они сказали:

Не тестируйте [частные методы] явно. Тестируя только через общедоступный интерфейс, вы все равно неизбежно в конечном итоге будете тестировать приватные. Это основная причина, по которой люди часто жалуются на то, что рефакторинг создает беспорядок. Потому что, когда тесты структурированы и зависят от *внутренней* структуры того, что вы тестируете, вы никогда не сможете свободно проводить рефакторинг и *не* придется обновлять кучу тестов. — Кристоф Гокель, главный мастер.

Для меня, когда дело доходит до тестирования частных методов, вопросы «следует» не так важны, как вопросы «есть». В большинстве случаев код и тесты уже существуют, и вам приходится жить в этой реальности. Является ли рефакторинг сложного для тестирования закрытого кода идеальным решением? Конечно. Но это также рискованно, потому что, как мы установили, в настоящее время он не проверен. Так является ли нынешнее «лучшее» использование вашего времени и денег, чтобы сделать эту работу прямо сейчас? Это не так точно во всех случаях. — Брайан Портер, главный мастер.

Когда вы решили, что вам нужен частный метод, и вы знаете, что это будет, [и] вы хотите его протестировать, это тот случай, когда вы неправильно используете TDD, ставя телегу впереди лошади. Тест ни на что не влияет — вы уже решили, чего хотите, и пытаетесь это проверить. Это тест после с дополнительными шагами. — Эрик Смит, главный мастер.

Если что-то частное внутри инкапсуляции достаточно сложное, чтобы его тестирование было полезным, воспользуйтесь этой возможностью, чтобы определить, можно ли описать это как конкретную область, и вставьте в нее логику и протестируйте. — Хэнк Лин, старший мастер.

Чтобы получить более подробные советы о том, что значит создавать полезные тесты, ознакомьтесь с потрясающим видео моего коллеги Роберта Веннера из 8th Light University: 100% Test Coverage .

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

ПОДВЕДЕНИЕ ИТОГОВ

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