Логотип Workflow

Article

Updated at:

Stage 15: File Upload and Download APIs

Этап 15 - API для upload и download файлов

Backend code становится тяжелым для поддержки, когда важное поведение спрятано в случайных controller methods. Эта статья про multipart upload и streamed download. Тема выглядит технической, но настоящий вопрос практический: что произойдет, когда реальный client отправит реальный request, а в нем что-то неполное, слишком большое, запрещенное, медленное или конфликтующее с текущим состоянием?

Files - не обычный JSON. У них есть size, media type, binary content, storage location, permissions и download headers. Небрежный upload endpoint может заполнить disk или принять опасный content.

Этап 15 - API для upload и download файлов

Картинка, которую нужно держать в голове

Представь маленький online shop. User открывает frontend, нажимает кнопку, и browser вызывает backend. Backend принимает HTTP data, преобразует их в Java objects, проверяет rules, трогает database и возвращает JSON. Если у каждого шага есть понятный владелец, систему можно читать. Если все шаги смешаны в одном методе, первый production incident становится болезненным.

Для этой темы последовательность такая:

  1. multipart request.
  2. size/type check.
  3. storage service.
  4. metadata row.
ЧастьОтветственность
ControllerПринимает HTTP data и возвращает публичный response.
ServiceВыполняет business use case и защищает business rules.
Repository/adapterРаботает с persistence или external systems.
DTO/contractОписывает, что внешний мир может отправить или получить.

Конкретный Spring пример

@PostMapping(value = "/api/files", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
FileResponse upload(@RequestPart MultipartFile file) {
    if (file.getSize() > maxUploadBytes) {
        throw new FileTooLargeException();
    }
    return fileService.store(file);
}

Код специально маленький, потому что главное здесь - граница ответственности. Controller не должен незаметно решать business rules, которые принадлежат service. Service не должен зависеть от servlet objects. Repository не должен знать, какое JSON field name ожидает frontend. Когда граница чистая, то же поведение можно документировать, тестировать и менять без правок во всех слоях.

Почему это важно

В demo project многие shortcuts выглядят безобидно. Вернуть все records нормально, пока в таблице пять строк. Вернуть entity нормально, пока в ней не появилось скрытое поле. Логировать без request id нормально, пока несколько users не падают одновременно. Работать с вручную созданной database нормально, пока staging не получает другую schema. Production work в основном состоит в том, чтобы убрать такие hidden assumptions до того, как они станут incidents.

Самое простое полезное правило: сделай behavior явным на границе, enforce его в правильном слое и держи public contract стабильным. Если frontend знает request format, response format и error behavior, он работает увереннее. Если tests проверяют тот же contract, refactoring становится безопаснее. Если logs и configuration отражают тот же design, operations меньше зависят от угадывания.

Частые ошибки

  • Помещать весь use case в controller method.
  • Позволять persistence details протекать в API contract.
  • Обрабатывать happy path, но оставлять failure behavior неопределенным.
  • Использовать local shortcut, который не сможет работать в staging или production.

Чеклист понимания

  • Я могу нарисовать последовательность этой темы от request до response.
  • Я могу объяснить, какой слой владеет решением.
  • Я могу назвать production problem, которую эта тема предотвращает.

Вопросы для самопроверки

  1. Что произойдет, если каждый endpoint реализует это правило по-своему?
  2. Какая часть behavior должна быть задокументирована для API clients?
  3. Какой test докажет, что правило работает, а не только happy path?