Backend custom-зоны нужны для правил, которые зависят от вашего бизнеса: дополнительной проверки заказа, расчёта значения, обращения во внешнюю систему или собственного API. Модель, обычные CRUD-операции и контракты продолжает обновлять генератор.
В путях ниже ENTITY_NAME замените именем каталога вашей сущности, например product; EntityName в Go-коде — её сгенерированный тип, например Product. Это обозначения, а не файлы, которые нужно создавать с такими именами.
Где писать код
| Файл | Для чего |
|---|---|
backend/internal/custom/ENTITY_NAME/hooks.go |
Логика в сценариях одной сущности |
backend/internal/custom/app/composition.go |
Подключение клиентов, зависимостей и hooks проекта |
backend/internal/custom/app/routes.go |
Собственные публичные и защищённые маршруты |
backend/internal/app/ENTITY_NAME/service.go |
Сгенерированный контракт: читайте его, реализацию добавляйте в custom |
backend/internal/generated/customcontracts/ |
Типы для подключения расширений через композицию |
Изменить значение перед сохранением
Откройте существующий hooks.go. Он уже содержит NewHooks() и методы для включённых CRUD-операций. Изменяйте тело нужного метода, не добавляйте второе объявление с тем же именем.
Пример для сущности с обычным строковым полем Name, которое не переведено в Localized:
func (hooks) BeforeCreate(_ context.Context, value domain.EntityName) (domain.EntityName, error) {
value.Name = strings.TrimSpace(value.Name)
return value, nil
}
Замените EntityName реальным типом и добавьте импорт strings. Если поле nullable или Localized, его тип может быть *string: сначала обработайте nil. Тип поля смотрите в сгенерированной модели, а не угадывайте по подписи в UI.
| Hook | Когда вызывается |
|---|---|
BeforeCreate, BeforeUpdate |
До вызова записи в репозиторий; могут изменить значение или вернуть ошибку |
AfterCreate, AfterUpdate |
После операции репозитория |
BeforeDelete |
Перед удалением; получает ключ записи |
AfterList |
После чтения страницы записей |
AfterGetByID |
После чтения одной записи |
Набор методов зависит от операций сущности. Ошибка BeforeCreate останавливает создание. Ошибка AfterCreate сама по себе не отменяет уже выполненную запись: для общей транзакции требуется явно спроектированная custom-логика. Для неверного пользовательского ввода используйте контракт ошибок вашего app-пакета, например оборачивание ErrInvalidInput, чтобы HTTP-граница корректно классифицировала ответ.
Подключить внешнюю зависимость
В composition.go реализуйте Configure(ctx, dependencies). Возвращаемая композиция позволяет задать типизированные hooks и защищённые маршруты. Непустой hook в композиции заменяет fallback из NewHooks(); nil оставляет его.
dependencies.DB — общий пул приложения. Используйте его через прикладные интерфейсы и не закрывайте самостоятельно. Клиентов, созданных вами, завершайте через возвращаемый Cleanup(ctx). Контекст Configure предназначен для старта; для запросов используйте r.Context(), а для своего фонового процесса — отдельный отменяемый контекст с завершением в Cleanup.
Добавить свой API
Для маршрутов проекта предусмотрены RegisterPublicRoutes и RegisterProtectedRoutes в custom/app/routes.go. У сущности есть RegisterRoutes. Выбирайте точку по задаче, не редактируйте generated bootstrap.
Папка custom не выдаёт маршрутам права автоматически. В зависимостях маршрутов доступны RequireAuthenticated, RequireAdminOperation, RequireExternalUser и CurrentPrincipal. Примените подходящую проверку доступа на сервере; факт входа пользователя ещё не означает разрешение на любую операцию. Собственные правила выбора записей также должны выполняться на сервере.
После изменения проверьте конкретный сценарий и соберите backend. Новый custom-маршрут не появляется автоматически в сгенерированном OpenAPI: его контракт поддерживает ваша команда.
Далее: Расширения интерфейса и Рабочий цикл custom-разработки.