Валидация, масштабируемость и юзабилити, это как бы совсем разное.
Для юзабилити - валидация на фронте. Введя что-то невалидное пользователь должен видеть проблему сразу, а не когда закончит заполнение формы, и отправит всё на бэк.
На бэке при этом должна присутствовать всё та же валидация, чтобы не было возможности сохранить невалидным данные прямыми запросами.
Для гибкости - контроллер валидирует только соответствие структуры запроса (ругается что не хватает значений, или ожидали данные в одном формате, а пришли в другом). Сами данные в процессе обработки оборачивать в самовалидируемые ValueObjectы, которые при создании будут уже проверять данные на соответствие более специфичным требованиям.
На каком бы этапе не шла валидация - при ошибках она не должна ничего передавать в ответ, а должна просто бросать исключение с текстом ошибки. Для того чтобы эти исключения преобразовать во внятный ответ - делается общий их обработчик, который будет приводить их к интерфейсу API.
И т.д. и т.п.