Как мы делали скоринг
Рассказ с картинками про один из наших проектов 2019 года. Вам же было интересно как и чем я зарабатываю на красивую жизнь? Вот это оно.
Вводная
На момент начала сотрудничества в 2018м, компания-заказчик B4Finance находилась на ранней стадии своего развития и являлась фактически стартапом.
Все что на тот момент было по части ПО это полурабочий прототип на AngularJS (который еще не на Typescript), бекэнд на Java и PostgreSQL, а также куча взятых на себя обязательств по разработке нового функционала.
Разумеется еще был традиционный для молодой компании бардак, чехарда с подрядчиками и постоянная смена сотрудников, но с этим думаю сталкивались все кто имел дело со стартапами.
Тем не менее компания выжила и стала вполне успешной, в том числе благодаря нашей своевременной помощи и участию.
все приведенные скриншоты взяты с тестовых стендов и не имеют отношения к реальным данным.
На 2024й год описываемый сервис скоринга уже выведен из эксплуатации.
Проект
Компания B4Finance занимается облачными решениями для KYC (Know-Your-Customer), что по-русски называется «проверка контрагентов», а ее клиенты это в первую очередь разнообразные инвестиционные фонды, выдающие деньги стартапам.
И ключевое слово тут «разнообразные», а ни разу не «фонды», дальше будет понятно почему.
Стартап по разработке ПО для оценки и проверки других стартапов — ну разве не гениально?
Внешне все это было очень похоже на кредитный конвеер в банке:
клиент заполняет кучу сложных форм онлайн и оставляет контактные данные о себе и своей компании, затем скоринг все это обрабатывает, вызывая внешние сервисы (аналоги нашего БКИ) и выдает оценку.
В базовом варианте оценка выглядела как-то так:
С учетом специфики бизнеса, для каждого отдельного заказчика разворачивалась практически полная выделенная копия всего сервиса целиком:
оформление и цвета, формы для заполнения, пользователи, данные и правила оценки — все кастомизировалось под конкретного заказчика.
Временами развертывание происходило не в облаке а на серверах самого заказчика, в закрытом контуре.
Общими оставались (чаще всего) лишь интеграционные сервисы для взаимодействия с внешними системами и сервер скоринга, про который и пойдет рассказ.
Одной из самых серьезных проблем было то, что B4Finance обслуживала не какого-то одного клиента и даже не двух, а десяток и очень разных — с разным профилем, разными запросами и разным масштабом бизнеса.
(десять это на момент начала сотрудничества, сейчас уже ~250).
У каждого из таких клиентов были свои особенности процесса KYC и оценки контрагента, временами противоречащие друг другу.
И всю эту логику проверок было необходимо создавать и затем поддерживать — от стадии планирования и разработки до последующей поддержки и доработок на продакшне.
Для каждого клиента отдельно.
Желательно максимально быстро и силами дешевых интернов, а не дорогих разработчиков — стартап же.
Реалии
Рассказ пойдет про третью версию скоринга (именно так она заявлена и у нас на сайте в портфолио), хотя конечно мы успели поучаствовать в разработке и первых двух.
Так вот, достаточно быстро выяснилось, что нужный уровень кастомизации логики расчетов скоринга выходит за рамки каких-то простых условий и требует полноценного программирования:
с константами, массивами, циклами и отдельными методами
Собственно первые две реализации скоринга и представляли собой жестко зашитую логику на Java, с вшитыми ветвлениями логики для разных клиентов.
Но уже примерно к седьмому клиенту код стал страшным как вьетнамская проститутка и требовать примерно такое же количество возлияний алкоголя для соития работы с ним.
Разумеется любая правка или адаптация для нового клиента требовали полноценной разработки, сборки и развертывания с задействием дорогих Java-разработчиков.
Решение
Мы поняли что дальше так жить нельзя и выдали решение, которое успешно применяем и ныне, в других наших проектах.
внутрь обычного веб-приложения на Java/Angular встаривается скриптовый движок на Jython.
Пользовательская логика реализуется на Jython, а Java и бины Spring превращаются в рабочее окружение, предоставляющее готовые методы платформы.
Jython это реализация Python 2.7 на Java, которая отлично встраивается и позволяет прозрачное взаимодействие Python — Java в обе стороны.
Фактически код на Jython превращается в своеобразный DSL (Domain Specific Language), с возможностью горячей замены выполняемого скрипта без перезапуска всего скоринг-сервера.
Поскольку Python преподают в любом ВУЗе хоть немного связанном с ИТ, найти интернов владеющих им хотя-бы на базовом уровне (а больше и не надо) не представляет проблемы.
Таким образом получаем идеальное решение:
- Готовое отделяемое приложение, реализующее управляемую среду для скриптов, разработкой которого занимаются нормальные полноценные программисты.
- Набор скриптов на DSL-языке (на базе Jython), реализующих конечную логику скоринга для клиента, за который отвечают «начинающие специалисты».
Разумеется мы не одни такие умные и подобные технологии присутствуют в любой крупной ERP/CRM/CMS системе, как только она вырастает до определенного предела, не позволяющего удовлетворять изощренные запросы клиентов без прямого перепрограммирования логики.
Точно также устроена например 1С:
за разработку самой платформы отвечают обычные Java программисты (текущие версии вроде на Java уже), а т. н. «конфигурация» для конечного заказчика пишется на языке 1С и руками 1С-программистов, для которых платформа 1С является средой выполнения.
Все тоже самое и в SAP R/3 и во всех остальных больших системах.
Теперь стоит показать как все это выглядело в действии. Начнем со списка «инстансов» скриптов скоринга:
Поскольку сервер скоринга обслуживал сразу несколько клиентов, он имел отдельные наборы «per client» скриптов с реализацией логики скоринга, каждый из которых запускался в своем окружении Jython.
Тут стоит отметить важную особенность:
запущенный инстанс интерпретатора Jython куда легче и менее треботельней к ресурсам чем запущенный процесс полноценного Python.
Поэтому мы спокойно запускали для тестирования по паре тысяч копий Jython внутри одной Java-машины и все это отлично и быстро работало.
По-сути такой подход это вообще единственная возможность оркестрации выполняемых скриптов в пределах одной JVM, без погружения в кластеризацию и тяжелые контейнеры.
Вот так выглядел интерфейс управления для одного скрипта скоринга:
Конечно же было разделение доступа и например техподдержка могла только перезапускать скрипты и просматривать журнал работы, но не изменять сам код скриптов.
Лог (серая область в центре) был динамическим и обновлялся через канал вебсокета, связанный с бекэндом, в который отправлялись сообщения во время работы скрипта в интерпретаторе Jython.
Примерно так выглядит лог сборки в Jenkins или в Visual Studio, если вам нужны примеры для сравнения.
Среда разработки
Всем кто уже использует подобные подходы с программируемой логикой известно, что существует проблема обслуживания клиентских скриптов:
Клиентский код в скриптах надо уметь выгружать и загружать в систему, отлаживать и запускать, причем делать все это в двух разных окружениях — в среде разработки и на продакшне.
Эти задачи куда сложнее чем кажутся на первый взгляд, а обосновать их необходимость конечному заказчику очень тяжело, поэтому встречается подобный функционал всего в двух местах:
в продуктовой разработке больших проектов и у нас.
Других таких еб#нутых, готовых реализовать фактически полноценное IDE для DSL в рамках заказной разработки на свете просто нет.
В общем случае вся цепочка работы выглядела следующим образом:
- Прикладной разработчик запускает локально специализированную среду разработки для DSL и реализует в ней всю необходимую логику скоринга;
- По завершению разработки, разработчик формирует специальный дамп — файл, содержащий все разработанные скрипты;
- Дамп передается администратору, отвечающему за продакшн и имеющему доступ к серверам;
- Администратор загружает дамп на сервер скоринга и производит необходимую настройку.
Да, это очень похоже на 1С, но только платформа имеет куда более узкую направленность в виде расчета скоринга.
Поскольку проект создавался на шаблоне JHipster, к нему очень легко и быстро была приделана обертка на Electron, которая вместе с локально запускаемым сервером создавала ощущение полноценного desktop-приложения.
При локальном запуске автоматически убиралась авторизация и использовалась встроенная облегченная база данных.
Интерну оставалось только кликнуть по ссылке для запуска приложения и начать работать.
Интерны были счастливы, поскольку все их рабочие обязанности свелись к прикладному программированию очень простой логики, не требующей глубоких знаний в Python или Java.
Заказчик был счастив, поскольку сильно экономил решая 99% задач по внедрению и кастомизации силами дешевых студентов-практикантов вместо дорогих программистов.
Ну и мы были счастливы, поскольку с наших плеч сняли скучную рутину по поддержке всех этих навозных масс однотипного говна, называемых «клиентским решением».
Поэтому примерно через месяц после релиза сервера скоринга, занялись еще одним проектом для B4Finance на таких же технологиях: сервером интеграции, про который будет отдельный рассказ в одной из следующих статей, благо что там тоже много интересного было реализовано.
Помимо десктопа, точно такая же среда была доступна и из браузера, уже на самом скоринг сервере:
И это тоже использовалось — для оперативных правок, когда было необходимо внести изменения в логику расчетов прямо на работающем сервере.
Например при изменении в методах API или endpoint-адреса внешней системы, используемой во время расчета скоринга.
Можно сколь угодно называть это «monkey patching» и считать дурной практикой, но просто поверьте:
когда вы сможете исправлять критические проблемы системы за одну минуту, без пересборки, без перезапуска c «cold start» — вас быстро начнут считать «магом», а вашу работу — «волшебством».
У этого разумеется есть своя цена и негативные последствия, но все же считаю, что возможность столь быстрого и безболезненного исправления критических проблем — важнее для бизнеса чем любые практики DevOps и все рекомендации для хороших проектов вместе взятые.
Также стоит рассказать и о библиотеке скриптов, содержащей общие для всех скорингов функции, константы и справочники.
Наверное половина всей логики расчетов находилась в этой библиотеке:
Скрипты библиотеки подгружались в окружение путем вызова специальной функции из скрипта скоринга:
Чем и достигалась максимальная гибкость решения:
в библиотеке содержались эталоны расчетной логики, в клиентских скриптах скоринга они либо подключались целиком без изменений либо кастомизировались в нужном месте и на необходимую глубину.
Поскольку скрипты скоринга были сильно проще чем код на Java — не было необходимости заморачиваться с обратной совместимостью или каким-то сложным тестированием.
Что стало бы обязательным при продолжении развития монолитной версии скоринга на чистой Java.
Ключи доступа для API и потребителей
Ввиду сложной схемы развертывания (в облаке, локально и на серверах конечного заказчика), а также из-за самого принципа работы скоринга (это асинхронная и длительная операция), мы реализовали двустороннюю схему аутентификации по API.
Звучит наверное страшно, но на практике все очень просто и логично.
Есть основная система, в которой собственно и находятся все формы с данными, называлась она KYC34 и в ее разработке мы тоже принимали активное участие.
Выглядела она тогда примерно так:
Из этой системы при определенных событиях (вроде перехода обработки в определенную стадию) отправлялся запрос на перерасчет показателей скоринга, вот этих, которые я уже показывал выше:
Обратите внимание на кнопку «Re-run», она как раз позволяла отправить повторный запрос на перерасчет скоринга вручную.
Для того чтобы отправить запрос, в настройках развернутого инстанса системы KYC34 должен был указываться ключ доступа к API скоринга.
Технически это был старый добрый JWT, генерируемый на стороне сервера скоринга.
Вот так выглядел интерфейс, отвечающий за генерацию ключей доступа:
Обратите внимание на поле «Expired At» — все ключи имели срок годности, что сокращало риск утечки, если например кто-то доберется до старых бекапов спустя годы.
Сам запрос на скоринг содержал минимум информации, фактически там был только ID записи, по которой нужен перерасчет и пара-тройка дополнительных флагов.
После получения запроса, скоринг сервер сам подключался к KYC34, используя таблицу соответствия ключа запроса и адреса и забирал оттуда необходимые данные.
После завершения расчетов, обновленные показатели отправлялись обратно в систему KYC34, c использованием вебхука — специального API для приема данных.
Для столь сложной схемы было несколько важных причин:
1. Объем данных, по которым производился расчет скоринга был очень большим и имел сложную, часто произвольную структуру, при этом в разных частях и версиях расчетной логики, использовалась лишь часть этих данных.
Так что реализовывать универсальное API по «приему всего» было как мининимум неразумно.
2. Стоял вопрос безопасности, поскольку такие данные по компаниям в одном месте в одно время еще и вместе с их скорингом — готовый продукт для продажи в даркнете.
И рано или поздно кто-то из интернов бы не удержался.
Лучший способ недопустить утечки конфиденциальных данных — не формировать без надобности хранилище таких данных.
В нашем варианте потенциальному вору пришлось бы очень сильно заморачиваться для того чтобы сформировать полную выгрузку всех данных вместе с оценкой.
Стоит также добавить, что на стороне KYC34 хранились лишь финальные показатели, используемые для отображения, но не весь скоринг целиком.
3. Скорость и логика работы скоринга допускала серьезные задержки в его работе, поскольку внешние системы проверки допускали задержку в проверке запрашиваемых компаний до суток.
Что не должно было влиять на оценку других компаний, происходящих параллельно на этом же сервере.
При таких вводных, все системы, основанные на очередях сообщений встают и идут на хер, поскольку ни одна очередь не позволяет таких задержек без потери общей производительности решения.
Для подключения к KYC34 также использовались отдельные ключи API, но создаваемые уже на стороне клиентской системы а не скоринга.
Вот так выглядел интерфейс настройки для них:
Дампы
Наконец последним на сегодня, но не последним по важности функционалом, который точно стоит упоминания является механизм загрузки и выгрузки дампов.
Дамп — специальный файл, в котором хранятся пользовательские данные: скрипты скоринга, справочники, даже отдельные бинарные файлы (вроде шаблонов документов), а также информация о самом дампе: кто и когда его создал, из какой системы выгрузил и что находится внутри.
Технически это был архив, формируемый администратором скоринг-сервера по нажатию кнопки в специальном интерфейсе. Ближайший функциональный аналог — бекапы в Atlassian Jira.
Вот так выглядел этот самый интерфейс:
На скриншоте выше показана информация о текущем (последнем установленном) дампе, которая бралась из метаданных дампа.
Вот так выглядел процесс выгрузки, т.е. формирования дампа:
Собственно вариации этого же механизма дампов использовались и в нашем CMS-движке для сайта, а также в ряде других проектов.
реализовывать такое с нуля стоит очень большой крови, абсолютно точно будут серьезные ошибки и просчеты.
Поэтому в общем случае стоит обходиться стандартными механизмами формирования выгрузок на уровне СУБД.
Подводя итоги
Это был очень интересный и сложный проект, который мы бы просто не смогли реализовать без нашего уникального опыта и компетенций, вне зависимости от предлагаемых бюджетов.
Поэтому случись такой заказ например за десять лет до — мы бы не вытянули.
Ну а сам сервер скоринга, вместе с описанной логикой разработки стал по-сути центральным звеном и на какое-то время обеспечил выживание компании.
Что и позволило в итоге ей вырасти до нынешних масштабов.
Но сервер скоринга был не единственной интересной разработкой для B4Finance, поэтому в одной из следующих статей расскажу и вот об этом проекте: