
Первая статья из мини‑серии про валидацию на базе Protobuf. В этой части — концепция spec‑first и protoc‑gen‑validate. В следующей поговорим про protovalidate и то, почему его вообще имеет смысл рассматривать как «следующее поколение» (или же как очередная эволюция в обратную сторону?)
Также, чтобы не пропустить следующую часть, очень рекомендую подписаться на мой телеграмм канал :)
В общем, зачем я поднимаю эту тему то?
Когда говорят про Protobuf, чаще всего всплывают несколько важных бенефитов:
легковесный бинарный формат, экономим трафик
удобно: описал .proto — сгенерил код — готово gRPC‑API
И это правда. Но для меня сейчас один из самых важных бенефитов Protobuf, это spec‑first документирование, ведь фактически у нас сразу заявленная схема работы API нашего сервиса. Она отдельно проходит ревью, по ней живут несколько сервисов (а с gRPC-Gateway и WEB), но есть ньюанс, почти все контракты что я видел в работе - это лишь половина реально нужной информации, ведь: схема без ограничений на значения — это только половина контракта.
Тип string в поле email сам по себе не говорит ничего:
можно ли пустую строку?
есть ли ограничение по длине?
должен ли это быть вообще email?
И чаще всего на эти вопросы ответы в самом коде сервиса, так что, говорить мы “spec‑first” - довольно кривовато :)
В этой статье хочу показать, как на этот вопрос смотрю я, и почему считаю, что описание API неполное, пока не описаны ограничения значений. А ещё — как в эту картину вписывается protoc-gen-validate и почему даже простое его внедрение уже сильно упорядочивает ваши контракты
Как мы обычно валидируем без spec‑first
Возьмём типичный gRPC‑сервис на Go. В нем у нас будет просто один хедндлер для регистрациию юзера

Вот базовая схема без какой‑либо валидации. Глядя на неё, мы понимаем только типы полей, но не можем ответить на вопросы:
Можно ли отправить пустой
email?Должен ли
emailсоответствовать какому‑то формату?Обязательно ли поле
name?etc….
Без spec‑first валидации все эти правила живут где‑то в коде сервера.
Что обычно происходит дальше:
-
Внутри хендлера появляются первые
if:
Потом добавляются проверки длины, форматы, диапазоны
Со временем логика валидации начинает дублироваться: один и тот же
emailпроверяется и в gateway, и в сервисе A, и в сервисе B — потому что «а вдруг оттуда придёт невалидный запрос».В итоге появляются общие библиотеки-валидаторы, но они живут отдельно от схемы, и синхронизировать их с
.protoприходится вручную.
Какие проблемы мы получаем:
Размазывание правил. Чтобы понять, что реально считается валидным запросом, приходится читать код нескольких слоев кода.
Рассинхрон. Один сервис обновил проверку (например, разрешил пустой
middle_name), другой забыли. Gateway остался со старой логикой. Где‑то что‑то начинает странно падать.Плохая обозримость. Открыв
.proto, мы видим только типы. Понять, какие значения вообще допустимы, практически нереально.
И вот тут лично у меня возникает ощущение, что Protobuf в таком виде не выполняет свою главную роль документации.
Он описывает форму, но не смысл :)
А теперь давайте посмотрим, как та же схемы выглядит с protoc-gen-validate , так что ставим сам пакет для работы c validate

и дальше качаем пакет вызвав easyp mod update
Подробнее про работу с пакетами в protobuf можно почитать тут.
Дальше нам нужно прописать в контракте уже сами правила валидации

Это и есть spec‑first валидация: правила живут там же, где и описание структуры данных
Теперь контракт описывает и структуру, и ограничения. Любой, кто откроет этот .proto, сразу поймёт:
emailобязателен, не длиннее 255 символов и должен быть валидным email‑адресомnameтоже обязателен и не может быть длиннее 100 символов
Картина для меня теперь выглядит так:
Схема описывает и структуру, и ограничения значений. Всё это живёт рядом, в одном файле, версионируется и ревьюится вместе.
Что хочется видеть в идеальном мире:
-
Открываю
.protoи сразу понимаю:какое поле обязательно;
какие у него границы (минимум/максимум, длина строки);
какой формат (email, UUID, phone и т.п.).
-
Эти ограничения машиночитаемы:
сервер может автоматически валидировать входящие сообщения до бизнес‑логики;
клиент (другой бэкенд, CLI, SDK) может вызвать ту же валидацию до отправки по сети и не тратить лишний RTT.
Всё это — не отдельные JSON‑схемы, не отдельные
.yamlгде‑то в Wiki, а именно часть Protobuf‑контракта.
Идея у protoc-gen-validate (PGV) довольно прямая:
В
.protoрядом с полями описываются правила валидации через специальные опции.Плагин
protoc-gen-validateпри генерации кода добавляет к сообщениям методы видаValidate().Вы вызываете
msg.Validate()там, где вам нужно проверить входящие данные.
Очень условный пример:

После генерации у вас на Go появляется что‑то вроде:

Детали реализации зависят от конкретной версии PGV, но концептуально картинка такая: контракт описан в .proto, а код вокруг только исполняет его.
Окей, а как это выглядит в реальном gRPC‑сервисе?

Вы конечно можете так делать конечно же всегда, но будем объективны, это явно логичнее переложить в интерсепторы

В Go это можно реализовать через готовый интерсептор из grpc-ecosystem:

Дальше вы просто навешиваете этот интерсептор при поднятии сервера — и получаете:
единое место, где проверяются все входящие сообщения;
гарантии, что бизнес‑логика не увидит заведомо «битые» данные;
единый формат ошибок валидации.
Важно: это не отменяет доменную/бизнес‑валидацию. PGV прекрасно решает именно структурные вещи: длины, диапазоны, форматы, простые зависимости полей. Всё, что требует похода в БД, общения с внешними сервисами или сложной предметной логики, по‑прежнему живёт в коде.
Если подвести промежуточный итог, protoc-gen-validate даёт довольно много плюсов почти «из коробки»:
поднимает валидацию на уровень схемы — правила живут в
.proto;создаёт единый язык описания ограничений (аннотации вместо разнородных
ifи struct tag’ов);позволяет встроить валидацию в инфраструктуру (интерсепторы, middleware), а не размазывать по коду;
даёт возможность использовать те же проверки в клиентском коде.
При этом у такого подхода есть вполне естественные ограничения:
Привязка к генерации кода под каждый язык. Для polyglot‑систем поддержка «везде и сразу» уже не такая простая задача.
Невозможно (или очень неудобно) описывать более сложные, динамические правила без написания собственного кода вокруг.
И это нормально. Для большого количества проектов PGV до сих пор будет «тем самым инструментом, который надо просто взять и использовать».
Но если вы смотрите дальше — хотите более универсальный рантайм, меньше генерации кода, более гибкие правила и нормальную жизнь в polyglot‑мирах, — логично начать смотреть по сторонам.
И вот тут в историю входит protovalidate. Но! protovalide фактически попытка переродить механику валидаций в protobuf со всеми особенностями бизнес проверок, но о нем уже будет в продолжении в телеграмм канале :)