Система активно развивается, появляются новые сервисы и фичи. При этом во многих сценариях требуется использовать уже существующие сущности — например, карточки курсов, категории, пользователей — в новых контекстах, таких как рекомендации, избранное и история.Ранее для подобных задач создавались отдельные структуры данных и DTO, что приводило к дублированию, увеличению кода и росту вероятности расхождения в представлении одной и той же сущности в разных частях системы.
Было принято использовать подход композиции: переиспользовать существующие сущности в том виде, в котором они возвращаются своим основным сервисом, и при необходимости дополнять их контекстно-зависимыми полями. Это позволяет избежать дублирования и сохранять единообразие структуры данных.Пример:
GET /recommendations
{
"data": {
"items": [
{
"reason": "На основе интересов пользователя",
"course": {
"id": "course-123",
"slug": "intro-to-design",
"title": "Введение в дизайн",
"description": "Курс о базовых принципах дизайна"
// остальные поля, как в каталоге
}
}
]
}
}
В этом примере:
course — объект, полученный из сервиса курсов
reason — поле, добавленное в контексте рекомендаций
Формирование ответа происходит на стороне сервиса рекомендаций или дополнительного сервиса-контроллера перед API Gateway со стороны бекенда. Данный сервис агрегирует данные: запрашивает идентификаторы курсов, получает их полное описание из сервиса курсов, и добавляет контекстные поля.
В рамках единого подхода к переиспользованию сущностей в API мы ввели соглашение об организации схем в каталоге Resources. Каждая сущность описывается в своем доменном сервисе и используется как строительный блок при компоновке более сложных структур.
Resources/
Courses/
Course // Полная схема курса для страницы саммари
CourseListItem // Упрощенная карточка курса для списков и подборок
Recommendations/
RecommendationListItem // Обертка над CourseListItem с дополнительными полями, такими как причина рекомендации
Каждая схема, описанная в Resources/<Service>, считается "источником истины" для соответствующего типа данных.
При необходимости использовать сущность в другом домене она не копируется, а включается по ссылке.
Ссылочная схема указывает на то, что данные должны быть получены из оригинального сервиса. Например, если Recommendation содержит поле course: CourseListItem, то сервис рекомендаций запрашивает курсы по ID через сервис курсов и вставляет полученные данные.