Проверка на границе API — это выбор, который определяет и скорость отклика, и правильность решения. Одна и та же строка проходит через клиент, сервер и внешний источник, и на каждом участке известно что-то своё. Ниже разберём, где чему место, почему два слоя могут расходиться без всякой ошибки, как разделить локальную арифметику и обращение к реестру и что делать с правилами, которые со временем меняются.
Проверка должна жить на клиенте или на сервере?
Вопрос сформулирован как выбор, но правильный ответ — оба, с разными задачами.
Клиент отвечает за мгновенную реакцию. Пока пользователь вводит строку, клиент может убрать разделители, проверить состав знаков, длину и сразу показать, что строка не похожа на номер. Это дешёвая работа: она выполняется на месте, не требует сети и не создаёт нагрузки.
Сервер отвечает за окончательное решение. Он проверяет то, чему нельзя доверять в браузере: точное соответствие правилам, контрольную цифру, при необходимости — обращение к внешнему источнику. Только серверный результат можно записывать в базу и использовать в бизнес-логике.
Причина такого разделения проста. Всё, что приходит с клиента, может быть изменено: подсказку легко обойти, и это не взлом, а обычное поведение браузера. Поэтому клиентская проверка — удобство для человека, а не защита данных.
Отдельный вывод: два слоя могут прийти к разным результатам, и это не дефект. Пользователь мог получить подсказку на неполной строке, правила на сервере могли обновиться, а внешний источник — ответить иначе, чем ожидалось. Задача проектирования — не избежать расхождения любой ценой, а сделать так, чтобы расхождение было объяснимым.
Что проверять локально, а что отдавать наружу?
Локально имеет смысл проверять всё, что выводится из самой строки без обращения к внешним данным.
| Что проверяется | Где | Почему |
|---|---|---|
| Состав знаков и длина | клиент | ответ мгновенный, сеть не нужна |
| Очистка от разделителей | клиент | дешёвая операция, улучшает ввод |
| Контрольная цифра | клиент и сервер | алгоритм локальный, но решению нужен сервер |
| Принадлежность к системе | сервер | затрагивает правила учёта |
| Существование записи | сервер, внешний источник | данных внутри системы нет |
| Бизнес-правила | сервер | зависят от состояния учётных записей |
Ключевое различие — между локальной арифметикой и внешним запросом. Локальная проверка быстра, работает без сети, даёт одинаковый результат при повторе и не зависит от чужих систем. Внешний запрос медленный, может истечь по времени, упирается в ограничение частоты и зависит от доступности чужого сервиса.
Смешивать их нельзя ни в коде, ни в интерфейсе, ни в текстах ошибок. Пользователь должен видеть разницу между «эта строка не проходит арифметическую проверку» и «мы не смогли получить ответ из реестра». Первое он может исправить, второе — нет.
Общая логика проверки самой строки, включая очистку и определение системы, разобрана в материале про устройство проверки номера.
Почему локальный результат нельзя считать доказательством существования?
Потому что контрольная цифра подтверждает только одно: строка внутренне непротиворечива. Она собрана по правилам системы, и больше ничего из неё не следует.
Из этого растёт самая распространённая ошибка интеграций: успешная локальная проверка трактуется как подтверждение того, что номер кому-то принадлежит. Такое утверждение неверно в принципе. Строка может быть вымышленной, но построенной по правилам; может относиться к закрытой или отозванной записи; может подходить сразу нескольким системам с похожими параметрами.
Правильная формулировка для пользователя и для документации: проверка подтверждает согласованность записи, а не существование объекта. Всё, что связано с существованием, относится к компетенции внешнего источника, и только его ответ можно на это переносить.
Это же ограничение нужно учитывать и в смежных задачах. Когда алгоритма в системе нет вовсе, различие между форматом и значением становится ещё заметнее, о чём говорит статья про номера без контрольной цифры.
Как классифицировать ошибки и что из них можно повторить?
Классификация ошибок определяет, что делать дальше, и здесь важны три группы.
Первая — ошибки ввода. Неподходящий знак, неверная длина, не сходящаяся контрольная цифра. Пользователь может их исправить, повторы бессмысленны: пока строка не изменена, ответ будет тем же. Именно к этой группе относится и случай, когда подходящей системы не нашлось: правило для такого номера отсутствует, и повтор не поможет.
Вторая — временные сбои внешнего источника. Истёк таймаут, получен отказ по частоте запросов, сервис недоступен. Пользователь здесь ничего исправить не может, а повтор имеет смысл, обычно с нарастающей паузой. Важно, чтобы такие сбои не превращались в отказ по существу: «не удалось проверить» и «проверка не пройдена» — разные вещи.
Третья — отказ по существу от внешнего источника. Запись не найдена или не может быть использована. Это окончательный ответ, и повторять его бессмысленно; пользователю нужно сообщить результат, а не проблему связи.
Дополнительно стоит различать, кто именно не смог ответить: наш собственный сервер или внешняя система. Это влияет и на текст сообщения, и на то, куда смотреть при разборе инцидента.
Как обращаться с таймаутами, ограничениями и кэшем?
Все три механизма нужны не для удобства, а для устойчивости границы.
Таймаут обязателен. Без него ожидание чужого ответа может затянуться, и вся операция окажется в подвешенном состоянии. Длительность выбирается так, чтобы обычный ответ успевал прийти с запасом, а необычно долгий обрывался.
Ограничение частоты нужно учитывать заранее, а не узнавать о нём из отказов. Практические меры: не отправлять запрос по каждой нажатой клавише, объединять близкие обращения, откладывать повторы с нарастающей паузой.
Кэш помогает тем, что позволяет не спрашивать одно и то же дважды. У него есть цена: сохранённый ответ может устареть, и если правила изменились, старое решение будет воспроизводиться. Поэтому кэш должен иметь срок жизни, а не храниться бессрочно.
И главное — нужно записывать, на основании чего было принято решение: локальная проверка, свежий внешний ответ или сохранённый ранее. Без такой записи невозможно объяснить, почему система когда-то пропустила или отклонила конкретную строку.
Как разработчику выстроить эту границу
Первое — определите состояния заранее и не сводите их друг к другу. Минимальный набор: согласовано, не сходится, формат нарушен, формат верен без алгоритма, система неизвестна, внешняя проверка недоступна. Каждому состоянию — свой код в ответе, а не один общий признак ошибки.
Второе — сделайте ответ сервера самодостаточным. В нём должно быть понятно, какая проверка выполнялась, какие правила применялись и что именно не сошлось. Тогда клиенту не придётся догадываться, а поддержке — восстанавливать ход событий по логам.
Третье — версионируйте правила. Со временем требования к записи номеров меняются, и вместе с ними меняется результат проверки. Если правило не отмечено версией, невозможно восстановить, почему решение год назад было именно таким. Историческое решение должно воспроизводиться, иначе разбор старых случаев превращается в догадки.
Четвёртое — проверяйте всю границу целиком, а не только арифметику. Ошибки чаще живут на стыках: клиент и сервер по-разному очищают строку, кэш отдаёт устаревший ответ, сообщение об ошибке не отличает сбой связи от отказа по существу. Каждый из этих стыков стоит проверять отдельно, подставляя вымышленные строки, чтобы не зависеть от настоящих данных.
Что делать дальше
Проведите мысленную границу через свою систему и отметьте, какая проверка где выполняется и по какому признаку принимается решение. Затем проверьте, различает ли пользователь сбой связи и отказ по существу: если в интерфейсе это одно и то же сообщение, разделение стоит добавить.
Посмотреть, как выглядят разные состояния для одной строки, удобно на инструменте проверки, а как устроен разбор больших наборов — в статье про пакетную проверку номеров. Все строки в примерах этой статьи вымышлены и приведены только для объяснения; результат проверки говорит о согласованности записи и не подтверждает, что номер существует, кому-то принадлежит или может быть использован.