Организация Go модуля
Часто задаваемый вопрос разработчиками, новичками в Go: «Как организовать проект на Go?», в терминах структуры файлов и папок. Цель данного документа — предоставить некоторые рекомендации, которые помогут ответить на этот вопрос. Чтобы максимально эффективно использовать этот документ, убедитесь, что вы знакомы с основами Go модулей, прочитав руководство и управление исходным кодом модуля.
Проекты на Go могут включать пакеты, командные программы или их комбинацию. Данное руководство структурировано по типу проекта.
Базовый пакет
Базовый Go пакет содержит весь свой код в корневой директории проекта. Проект состоит из одного модуля, который в свою очередь содержит один пакет. Имя пакета совпадает с последним компонентом пути имени модуля. Для очень простого пакета, требующего одного Go файла, структура проекта выглядит так:
<code>project-root-directory/ go.mod modname.go modname_test.go </code>
[в течение всего документа имена файлов/пакетов являются произвольными]
Предположим, что эта директория загружена в репозиторий GitHub по адресу github.com/someuser/modname, тогда строка module в файле go.mod должна содержать module github.com/someuser/modname.
Код в файле modname.go объявляет пакет следующим образом:
<code>package modname // ... код пакета здесь </code>
Пользователи могут затем использовать этот пакет, импортируя его в свой Go код с помощью:
<code>import "github.com/someuser/modname" </code>
Go пакет может быть разделён на несколько файлов, все находящиеся в одной директории, например:
<code>project-root-directory/ go.mod modname.go modname_test.go auth.go auth_test.go hash.go hash_test.go </code>
Все файлы в директории объявляют package modname.
Базовая команда
Базовая исполняемая программа (или командная утилита) структурируется в соответствии со своей сложностью и размером кода. Самая простая программа может состоять из одного Go файла, в котором определена функция func main. Более крупные программы могут иметь свой код, разделённый на несколько файлов, все объявляющие package main:
<code>project-root-directory/ go.mod auth.go auth_test.go client.go main.go </code>
В данном случае файл main.go содержит func main, но это просто соглашение. «Главный» файл также может называться modname.go (с подходящим значением modname) или любым другим именем.
Предположим, что эта директория загружена в репозиторий GitHub по адресу github.com/someuser/modname, тогда строка module в файле go.mod должна содержать:
<code>module github.com/someuser/modname </code>
И пользователь должен иметь возможность установить её на свою машину с помощью:
<code>$ go install github.com/someuser/modname@latest </code>
Пакет или команда с вспомогательными пакетами
Более крупные пакеты или команды могут выиграть от выделения части функциональности в вспомогательные пакеты. Изначально рекомендуется размещать такие пакеты в директории с названием internal;
это предотвращает зависимость других модулей от пакетов, которые мы не обязательно хотим экспонировать и поддерживать для внешнего использования. Поскольку другие проекты не могут импортировать код из нашей директории internal, мы свободны рефакторить его API и в целом перемещать вещи без нарушения работы внешних пользователей. Структура проекта для пакета выглядит следующим образом:
<code>project-root-directory/ internal/ auth/ auth.go auth_test.go hash/ hash.go hash_test.go go.mod modname.go modname_test.go </code>
Файл modname.go объявляет package modname, auth.go объявляет package auth и так далее. modname.go может импортировать пакет auth следующим образом:
<code>import "github.com/someuser/modname/internal/auth" </code>
Расположение команды с вспомогательными пакетами в директории internal очень похоже, за исключением того, что файл(ы) в корневой директории объявляют package main.
Несколько пакетов
Модуль может состоять из нескольких импортируемых пакетов; каждый пакет имеет свою директорию и может быть структурирован иерархически. Вот пример структуры проекта:
<code>project-root-directory/ go.mod modname.go modname_test.go auth/ auth.go auth_test.go token/ token.go token_test.go hash/ hash.go internal/ trace/ trace.go </code>
Напоминаем, что предполагается, что строка module в go.mod выглядит так:
<code>module github.com/someuser/modname </code>
Пакет modname находится в корневой директории, объявляет package modname
и может быть импортирован пользователями с помощью:
<code>import "github.com/someuser/modname" </code>
Подпакеты могут быть импортированы пользователями следующим образом:
<code>import "github.com/someuser/modname/auth" import "github.com/someuser/modname/auth/token" import "github.com/someuser/modname/hash" </code>
Пакет trace, который находится в internal/trace, не может быть импортирован вне этого модуля. Рекомендуется максимально держать пакеты в internal.
Несколько команд
Несколько программ в одном репозитории, как правило, имеют отдельные директории:
<code>project-root-directory/ go.mod internal/ ... общие внутренние пакеты prog1/ main.go prog2/ main.go </code>
В каждой директории файлы Go программы объявляют package main. Верхнеуровневая директория internal может содержать общие пакеты, используемые всеми командами в репозитории.
Пользователи могут установить эти программы следующим образом:
<code>$ go install github.com/someuser/modname/prog1@latest $ go install github.com/someuser/modname/prog2@latest </code>
Обычной практикой является размещение всех команд в репозитории внутри каталога cmd;
хотя это и не обязательно в репозитории, состоящем исключительно из команд, это очень полезно
в смешанном репозитории, содержащем как команды, так и импортируемые пакеты, как мы обсудим далее.
Пакеты и команды в одном репозитории
Иногда репозиторий предоставляет как импортируемые пакеты, так и устанавливаемые команды с связанной функциональностью. Вот пример структуры проекта для такого репозитория:
<code>project-root-directory/ go.mod modname.go modname_test.go auth/ auth.go auth_test.go internal/ ... internal пакеты cmd/ prog1/ main.go prog2/ main.go </code>
Предполагая, что данный модуль называется github.com/someuser/modname, пользователи теперь могут как
импортировать пакеты из него:
<code>import "github.com/someuser/modname" import "github.com/someuser/modname/auth" </code>
Так и устанавливать программы из него:
<code>$ go install github.com/someuser/modname/cmd/prog1@latest $ go install github.com/someuser/modname/cmd/prog2@latest </code>
Проект сервера
Go является распространённым языком выбора для реализации серверов. Существует очень большая вариативность в структуре таких проектов, учитывая множество аспектов разработки серверов: протоколы (REST? gRPC?), развёртывание, файлы фронтенда, контейнеризация, скрипты и так далее. Мы сосредоточимся на частях проекта, написанных на Go.
Проекты серверов обычно не будут иметь пакетов для экспорта, поскольку сервер обычно представляет собой
самостоятельную исполняемую программу (или группу исполняемых программ). Поэтому рекомендуется хранить
пакеты Go, реализующие логику сервера, в каталоге internal. Более того, поскольку проект, вероятно,
будет содержать множество других каталогов с файлами, не относящимися к Go, хорошей практикой будет
хранить все команды Go в каталоге cmd:
<code>project-root-directory/ go.mod internal/ auth/ ... metrics/ ... model/ ... cmd/ api-server/ main.go metrics-analyzer/ main.go ... ... остальные каталоги проекта с кодом на других языках </code>
Если репозиторий сервера будет расти и появятся пакеты, которые окажутся полезными для использования в других проектах, лучше вынести их в отдельные модули.