> Как архитектурно передавать на фронт сложную структуру с вложенными полями и массивами (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Короткий ответ
Для передачи сложной вложенной структуры на фронт используем JSON с явно описанными DTO (data transfer objects) на стороне Go. Ключевые решения: строгая типизация через struct с json-тегами, использование omitempty для опциональных полей, избегание map[string]interface{} для вложенных данных. Для массивов - слайсы структур, для вложенности - указатели или значения структур. При необходимости - кастомная маршализация через json.Marshaler для контроля формата. Важно не отдавать доменные модели напрямую, а формировать отдельный слой представления.
Подробное объяснение
Архитектурно задача сводится к трём уровням: доменная модель, DTO и транспортный формат. Доменная модель содержит бизнес-логику и внутренние зависимости - её нельзя напрямую сериализовать, так как это раскрывает внутренности и создаёт жёсткую связку между слоями. DTO - это плоские структуры, которые описывают только то, что нужно отдать клиенту. Они могут агрегировать данные из нескольких доменных объектов, переименовывать поля, скрывать чувствительные данные.
Для вложенных структур в Go используем композицию структур: поле типа ChildStruct или []ChildStruct. JSON-теги задают имена полей в ответе. Для опциональных полей - указатели (*string) или omitempty. Для полиморфных структур - интерфейсы с кастомной маршализацией, но это усложняет поддержку, поэтому лучше избегать, если возможно.
Важный момент - версионирование API. Если структура сложная и будет эволюционировать, стоит сразу закладывать версию в URL или заголовок, чтобы менять DTO без поломки старых клиентов. Также стоит продумать пагинацию для массивов: не отдавать все элементы разом, а оборачивать в объект с items и total.
Для производительности: если структура очень большая, можно использовать json.Encoder с буферизацией, а не json.Marshal - это снижает пиковое потребление памяти. Для стриминга больших массивов - json.Decoder на стороне клиента, но это уже вопрос фронта.
На практике
На практике процесс выглядит так: сначала проектируем контракт - что именно нужно фронту. Затем создаём DTO-структуры, которые повторяют этот контракт. Маппинг из доменных моделей в DTO делаем вручную через функции-конструкторы или через библиотеки типа mapstructure, но ручной маппинг предпочтительнее - он явный и легко тестируется.
Для вложенных массивов часто возникает вопрос: отдавать ли пустой массив или null. Решаем через omitempty - если слайс пустой, поле просто не появится в JSON. Но это trade-off: фронт должен обрабатывать отсутствие поля. Альтернатива - всегда инициализировать слайс пустым значением, тогда в JSON будет [].
Ещё практический момент - обработка ошибок валидации. Если структура сложная, лучше не отдавать ошибку с кодом 400 и текстом, а структурировать: {"errors": {"field": "message"}}. Это тоже часть контракта.
Пример кода
GO// DTO для ответаtype UserResponse struct {ID string `json:"id"`Name string `json:"name"`Email string `json:"email,omitempty"`Profile *ProfileDTO `json:"profile,omitempty"`Roles []RoleDTO `json:"roles"`Settings map[string]string `json:"settings,omitempty"`}type ProfileDTO struct {AvatarURL string `json:"avatar_url"`Bio string `json:"bio,omitempty"`}type RoleDTO struct {Code string `json:"code"`Name string `json:"name"`}// Маппинг из доменной моделиfunc NewUserResponse(u *domain.User) *UserResponse {resp := &UserResponse{ID: u.ID.String(),Name: u.Name,Roles: make([]RoleDTO, 0, len(u.Roles)),}if u.Email != nil {resp.Email = *u.Email}if u.Profile != nil {resp.Profile = &ProfileDTO{AvatarURL: u.Profile.AvatarURL,Bio: u.Profile.Bio,}}for _, r := range u.Roles {resp.Roles = append(resp.Roles, RoleDTO{Code: r.Code,Name: r.Name,})}return resp}// В хендлереfunc GetUserHandler(w http.ResponseWriter, r *http.Request) {user, err := srv.GetUser(r.Context(), id)if err != nil {writeError(w, err)return}w.Header().Set("Content-Type", "application/json")json.NewEncoder(w).Encode(NewUserResponse(user))}
Как отвечать на собеседовании
Начните с главного: JSON + DTO, разделение доменной модели и представления. Затем объясните, как решаете вложенность - композиция структур, указатели для опциональности. Упомяните trade-off между omitempty и явными null. Если спросят про производительность - скажите про json.Encoder и стриминг. Если спросят про версионирование - предложите версию в URL или заголовке. Покажите, что думаете о контракте с фронтом: пагинация, структура ошибок, стабильность полей. Не углубляйтесь в детали реализации, если не просят - лучше показать понимание архитектурных принципов.
Что проверяет интервьюер
Проверяется понимание разделения ответственности между слоями: домен, DTO, транспорт. Умение проектировать контракт API, а не просто "отдать JSON". Понимание особенностей Go: как работают json-теги, указатели, omitempty, поведение слайсов при сериализации. Также оценивается способность видеть trade-off: например, почему не использовать map[string]interface{} - теряется типизация, сложно поддерживать, нет автокомплита. И ещё - умение думать о клиенте: что фронту удобно потреблять, как обрабатывать отсутствующие поля.
Типичные ошибки
- Отдавать доменные модели напрямую - раскрытие внутренностей, жёсткая связка.
- Использовать
map[string]interface{}для вложенных данных - теряется типизация, сложно рефакторить. - Не учитывать
omitempty- фронт получаетnullвместо отсутствия поля, или наоборот. - Игнорировать пагинацию для больших массивов - отдавать всё разом, что убивает производительность.
- Не продумывать версионирование - потом ломать клиентов при изменении структуры.
- Забывать про структуру ошибок - фронт не может понять, что именно не так.
- Использовать
json.Marshalдля больших ответов вместоjson.Encoder- лишнее потребление памяти.
> Похожие задачи по backend
Как контролировать уникальные указатели и умножать их ровно один раз в функции double в Go
В какой версии Go был исправлен баг с указателями в цикле
Как отследить и исправить deadlock в Go при работе с каналами и горутинами
Куда помещать горутину для чтения из канала в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью