Обмен реестрами в финансовых системах

registy

В этом посте хотелось бы рассказать ещё об одном виде межсистемного взаимодействия. В предыдущей публикации мы обсуждали как избежать проблем при подключении к внешним сервисам, сегодня поговорим о специфическом для некоторых отраслей способе обмена информацией, а именно обмен файлами.

Немного поясню что мы имеем ввиду. В финансовых организациях и финтехе распространено такое явление как обмен реестрами, выписками и прочим. Например:

  • При процессинге карт от платежной системы к нам поступает информация о блокировке средств на карте пользователя. Система блокирует сумму, а спустя некоторое время платежная система присылает файл клиринга, в которой содержатся операции, по которым нужно провести полное списание или же разблокировать средства
  • Банки обмениваются между собой файлами с информацией о движении денег по счетам и отражают эти движения у себя так далее

Файлы как правило представляют собой обычные таблицы с множеством строк, реализованные через разные форматы — CSV, DBF, Excel и прочие

Здесь мы собрали для вас небольшой чеклист, как реализовать разработку, тестирование и эксплуатацию такого вида взаимодействия

Реализация

Используйте асинхронный паттерн загрузки

Для загрузки файла лучше использовать асинхронный паттерн. В случае загрузки по HTTP это может выглядеть так: оператор загружает файл с клирингом, а в ответ получает идентификатор файла. После чего периодически обращается по определенному URL с этим идентификатором, чтобы узнать статус файла.

>> POST /registry/upload
>> registry.csv
<< 200 OK
<< {“fileId”:”42”}

>> GET /registry/status/42
<< 200 OK
<< {“status”:”pending”}

Обработка файла в синхронном режиме, когда пользователь ждёт результата не так удобна — запрос может отвалиться по таймауту, если файл большой т.п.

Потоковая обработка вместо загрузки в память

Если позволяет парсер и данные, то данные лучше парсить в потоковом режиме и не загружать весь файл в память (например использовать SAX вместо построения всей модели в памяти, если вы парсите XML). В некоторых случаях так сделать невозможно из-за природы самих данных(нужно провалидировать всё содержимое сразу) или из-за особенностей реализации парсера. В таком случае можно ограничить размер файлов и организовать очередь обработки таким образом, чтобы файлы умещались в память. Если вдруг в систему пришел файл большего размера, то можно оповестить администратора через мониторинг или систему алертов

Храните исходный файл

Если в реальной боевой среде случится ошибка, то вам будет гораздо проще найти причину, поскольку вы сможете просто выгрузить файл и написать тест/отдебажить проблемный участок

Подумайте про идемпотентность

Во избежание дублирования записей и файлов нужно продумать механизм идемпотентности. Чаще всего в различных выписках и клирингах присутствуют уникальные идентификаторы операции, на которые можно опереться. Но иногда формат и природа этих операций не подразумевает каких-либо идентификаторов, поэтому приходится изготавливать их самим.

В случае, если каждую запись невозможно однозначно идентифицировать, то можно организовать идемпотентный API загрузки самого файла. Например, можно вычислять контрольную сумму по содержимому файла, его имени или же в протоколе доставки сразу заложить ключ идемпотентности.

>> POST /registry/upload
>> X-REQUEST-ID: 34
>> registry.csv
<< 200 OK
<< {“fileId”:”42”}

>> POST /registry/upload
>> X-REQUEST-ID: 34
>> registry.csv
<< 409 CONFLICT
<< {“error”:”already_exists”}

В последнем случае ответственность за загрузку дубликатов перемещается на пользователя вашей системы

Подключите мониторинг

Параметрами такого мониторинга могут быть

  • Размер файла
  • Количество записей в файле
  • Статус обработки записей
  • Время обработки записей и файлов и тому подобное

Если во время загрузки и обработки что-то пойдет не так, то вы сможете легко это увидеть. Также полезно будет настроить уведомления на различные нестандартные ситуации или ошибки

Логируйте ошибки

Если при обработке записей или файла происходит какая-то ошибка, то нужно залогировать её с максимально подробной информацией. Удобнее всего хранить такую информацию вместе с самой строкой или файлом, но это не обязательно т.к. главное обеспечить быстрый доступ к подробностям ошибки.

Not OK

java.lang.NullPointerException: null

OK

An error occurred during processing record #42
Error details:
payload: {...}
parserSettings: {...}
thread: scheduled-job-thread-pool-12
requestId: 34,
user: admin
serverId: 46
version: 4.5.4
host: my.service.prod
timestamp: 2020-01-02T00:01:02
root cause: java.lang.NullPointerException: null
stacktrace:

...

Сохраняйте максимальный контекст

Пункт немного пересекается с предыдущим. К контексту относятся такие параметры как

  • Время начала обработки
  • Время завершения обработки
  • Статус обработки
  • Источник запуска (если есть, например, оператор)
  • Источники файла
  • Версия приложения
  • Информация о сервере, на котором происходила обработка и многое другое

Такая информация также поможет при разборе инцидентов и сильно упростит жизнь разработчику

Предусмотрите возможность ручного исправления и перезапуска

Довольно частая ситуация, когда нужно перезапустить обработку отдельных строк или файлов, по причине попадания туда некорректных данных. В таком случае полезно иметь у себя “ручку” перезапуска обработки строк и/или файлов. Не забудьте также сохранить историю таких перезапусков.

Реализуйте возможность ручного отключения обработки

На случай если в систему начали поступать некорректные данные или же вы нашли ошибку в вашем алгоритме возможность отключения обработки будет не лишней. В своей практике несколько раз наступал на подобные грабли. В результате нам приходилось перебирать и перезагружать такие записи вручную, так как не было возможности остановить обработку без отключения всего модуля системы

Валидируйте ввод

Как уже говорилось в предыдущем посте, чинить ошибку на ранних подступах гораздо дешевле и проще, нежели выловить непонятный  NullPointerException в недрах системы. Ещё один момент, с которым также неоднократно сталкивался — разработчики зачастую прибивают гвоздями максимальную длину полей и различные валидации прямо в БД. Этот ход сужает пространство для маневра. Пример из реальной жизни — поле, которое отвечало за хранение названия юрлица почему-то ограничили длиной 160 символов. И конечно же, спустя непродолжительное время в систему приехали данные, которые выходят за пределы установленного лимита. В случае, если бы проверка была в коде, достаточно было бы поправить код и обновить версию, чтобы новые данные были успешно обработаны. Но теперь нужно менять схему БД, что уже не так-то просто.

Другое правило касающееся типов данных — не хардкодить тип данных там где это не нужно. Скажем, для суммы есть смысл сразу предусмотреть поле вида BigDecimal, а к примеру для идентификатора лучше заложить строковый типа, даже если сам идентификатор выглядит как целочисленное значение

При ошибке сообщите клиенту что пошло не так

Клиенту будет гораздо проще работать, когда он видит подробное описание ошибки, а не абстрактный internal error. Какой файл, какая строка, почему упало, но без технических подробностей (стектрейс будет здесь лишним). Вообще при построении API или вообще какого-либо взаимодействия необходимо как можно понятнее объяснить клиенту что пошло не так — иначе вместо написания программ будете работать техподдержкой.

Тестирование

Теперь рассмотрим вещи, на которые стоит обратить внимание при тестировании.

Проверьте граничные случаи

Протестируйте и корректно обработайте случаи, когда вам прислали

  • Пустой файл
  • Файл в неверном формате
  • Файл с отсутствующими столбцами

Почему-то про это тоже часто забывают.

Протестируйте некорректные данные

Убедитесь что ваши парсеры ломаются, если в полях содержатся некорректные данные. В моей практике были случаи, когда дата в неверном формате воспринималась парсером как валидная и на выходе получалось неизвестно что. Тоже самое было с переполнением целочисленных типов — в коде поле имело тип int, а реально к приехал long. Мы ожидали что парсер ругнется и упадет, а получилось что он проглотил эту ошибку и просто скастил long к int, естественно с потерей всего чего только можно.

Протестируйте систему на реальных данных

Если возможно — в процессе тестирования попросите реальные данные, которые вы хотите обрабатывать за какой-нибудь значительный промежуток времени. С большой долей вероятности вы наткнетесь на кейсы, которые не были описаны в документации.

Проверьте производительность вашей системы

Есть бизнес-процессы, которые чувствительны к скорости обработки таких батчей. Например, при закрытии банковского дня нужно обработать N записей за M минут. Убедитесь что ваша система удовлетворяет требованиям по производительности. В самом процессе обработки есть смысл предусмотреть механизм backpressure на случай если система начинает захлебываться.

Итого

Простая на первый взгляд задача содержит в себе множество подводных камней. Не пожалейте времени на реализацию тех вещей, которые были описаны выше. Это не так сложно, зато потраченное время обязательно окупится в процессе эксплуатации.

Оставить комментарий

Ваш адрес email не будет опубликован.