В это нелегкое время, так сложно найти работу, когда первокурсники МИРЭА уже пишут курсовые с микросервисами на 1к рпс, а выпускники каждый по системе на миллиарды пользователей. Видимо старому Java-коту ничего не остается, кроме как беседовать с вечностью и её сестрой бесконечностью. Если не они, то кто поможет коту найти работу?
Статью можно прочесть в формате pdf.
Благодарности
В первую очередь автор благодарит РТУ МИРЭА, за полученное высшее техническое образования мирового образца. Труд учителей просвечивается в этой работе. Отдельная благодарность Жемчужниковой Татьяне Николаевне и Рачковской Евгении Федоровной за ваш неоценимый вклад.
Автор выражает огромную благодарность следующим компаниям и коллегам, честью с которыми было работать:
ГК Инфовотч - и особенно Хайретдинову Рустему, Пасечникову Константину;
ПАО Сбербанк - Коршунову Илье, Субботину Михаилу, Светозарову Александру, Трайкович Галине, Габуния Ксении, Юдину Александру;
Edna - Макарову Илье - великому разработчику великой компании, Ксенофонтову Илье, Силину Илье, Микурову Дмитрию, Коптеву Денису, Пятиловой Юлии, Фоляку Игорю, Козыреву Ивану, Жемойдикову Ивану, Чесноковой Татьяне, Юхиной Надежде, Булыгину Денису.
Введение
Человечество, вне зависимости от строя, убеждений или иных причин, движется по пути цифровизации и автоматизации процессов. Можно быть луддитом, но только на страницах истории. Бессмысленно спорить с законами истории и развития.
В данной работе автор предлагает эскиз платёжной системы, позволяющей проводить операции, большие, чем просто платежи. Любая платёжная система - это всегда передача прав от одной стороны к другой. Передавать можно не только деньги или финансы. Все что можно назвать ресурсом - возможно передавать и контролировать такую передачу. Попытка упрощать экономику до скалярных ВВП, ППС, инфляции, ключевой ставки или других умных слов, как показывает текущий затянувшийся кризис - не приводит ни к чему позитивному. Невозможно измерять сложную экономику скалярной величиной и считать, что это позволит вам достоверно описать управляемый объект, прогнозировать его движение. Управление экономикой должно быть усложнено, стать многофакторным. Из экономики должен уйти магический догматизм либеральной экономики, выжигающей любые другие экономические школы. Данная реформа потребует совершенно иного набора инструментов для управления. Потребует возможности в реальном времени смотреть за изменениями.
В своё время СССР, если отбросить предательство, сильно отставал в первую очередь из-за отсутствия инструментов по управлению экономикой. Не было возможности на лету вычислять планы и адаптироваться. Разумеется, если у вас несколько тысяч параметров - то построить систему не является большой сложностью. Эти данные помещаются в ЭВМ. Но ведь планирование экономики - это открытая задача с неограниченными степенями свободы.
Существующие финансовые системы не отличаются высокой производительностью, хотя самая последняя и совершенная реализация ведущего на сегодня подхода - платёжная система МИР уже достигла откликов, время которых измеряется сотнями миллисекунд. Почти все системы в той или иной степени централизованы и не предусматривают независимой работы. Отчасти это продиктовано юридическими документами, согласно которым есть МВФ, "национальные" ЦБ, выполняющие все постановления МВФ, словно являются филиалами МВФ, но это разумеется всего лишь совпадение, и уже затем банки различных размеров. Особенности взаимодействия между ЦБ приводят к тому, что некоторые ЦБ равнее других и могут навязывать государствам свою волю, даже если она является безумной и уничтожающей, что видно по действиям тех же США и Евросоюза.
Централизация и скаляризация экономики приводит к тому, что во главу ставится валюта, забывая, что это всего лишь расчётная единица. В итоге, на примере ОАЭ, огромные богатства вывозятся зарубеж, полученная валюта оседает зарубежом, что бы быть вложенными в зарубежные фонды, из которых забрать ничего нельзя. Но если попробовать забрать, то могут заморозить, арестовать или обнаружить какую-нибудь склянку, что бы помахать ей. А ведь могут ещё нарушения прав человека найти, натравить гринпис или прибить гвоздями активистов к брусчатке. В простонародье такая схема называется грабежом, рэкетом.
Автор не считает, что ресурсы должны стоить столько же, сколько готовый продукт. Это как минимум делает невозможным сложное производство. На такое производство невозможно взять людей с улицы, без образования. А обучение требует гораздо больших ресурсов, чем может себе позволить даже крупная корпорация.
На лицо назревает необходимость экономической реформы, которая не просто цифровизирует взаимодействия между людьми, но и даст на следующем витке глобализации, которая обязательно будет, ввиду естественных причин, хотя бы слабую защиту от ассирийского безумия глобальных элит. Обеспечить невозможность разрушения экономики и системы, которую сейчас активно ломают глобальные элиты, цепляясь за любую возможность продлить свою агонию неэффективного управления.
Данную работу невозможно провести одному человеку в полном объёме, поэтому предлагаемая эскизная платёжная система - является лишь одним из примеров большого семейства систем, которые можно реализовать. Прозорливый читатель в состоянии это увидеть.
Автор не ставит своей целью стать создателем такой платёжной системы, так как важен всегда только результат. Материалов в этой работе должно быть достаточно, что бы по крайне мерее лучшие 20% специалистов, в соответствующих областях, смогли справится с такой задачей. В Российской Федерации достаточно таких специалистов.
1 Обоснование возможности и необходимости реализации исходя из анализа текущей ситуации
Возможность реализации с точки зрения физики
Прежде чем приступать к любым проектам или иным работам по данной тематике, необходимо разобраться в теоретической возможности реализации
некоторых информационных систем с бесконечным масштабированием. В противном случае есть серьёзный риск оказаться в ситуации доказательства существования вечного двигателя.
Очевидно, что платёжная система, являющаяся частным случаем информационной системы, не создаёт транзакций из ниоткуда, а лишь отражает происходящее в реальном мире. Согласно законам физики есть предел на количество взаимодействий, осуществляемых в каждой точке пространства, в противном случае это приведёт к образованию условий, в которых осуществление человеческой деятельности станет невозможным. Т.к. информация не может существовать без материального носителя, это означает, что существует предел на максимальный объем информации, который может быть передан из одной точки в другую. Краевой случай нет разделяет передачу информации или материи, если массы достаточно для формирования чёрной дыры - она образуется. Разумеется для человека предельная граница расположена намного ниже. Использовать полный спектр состояний материи человечество не сможет, в противном случае придётся решать проблему Мюнхгаузена, что по сути аналог поиска вечного двигателя, но уже в болоте. Третьим фактором, который необходимо учитывать, является относительная равномерность распределения элементов периодической системы (таблицы Менделеева), что делает бессмысленным транспортировку материи из одной части галактики в другую, особенно учитывая, что стоимость добычи их примерно одинакова на масштабах вселенной.
Из вышеперечисленных факторов можно сделать вывод о том, что человек в своей хозяйственной деятельности будет всегда замкнут в некоторой границе экономических взаимодействий (по аналогии с границей видимой вселенной), причём вероятность, с которой он будет осуществлять какую-либо деятельность с каждой точкой внутри этой сферы, будет распределена по нормальному закону. Т.е. удельное количество операций на точку пространства, которое нужно будет обрабатывать платёжной системе не будет превышать некоторой константы, пусть и очень большой, в противном случае это приведёт к нарушению законов физики, что является невозможным. Данная константа инвариантна количеству участников и задаёт максимальное количество операций в каждой точке пространства.
Фантастические сценарии
Несмотря на то, что согласно текущему пониманию физики автором мгновенные перемещения как информации, так и материи, являются невозможными - необходимо предусмотреть такую возможность в будущем.
При рассмотрении следует остановиться на двух вариантах - мгновенная передача информации на большие расстояния и тоже самое, но уже с материей, разумеется речь идёт про телепортацию, варп-двигатели и прочее.
В случае возможности передавать информацию, никаких изменений можно не вносить.
Во втором случае возникает эффект искривления сферы. Для понимания можно обратиться к аналогии. Во время феодализма каждый феод был замкнут сам на себя и производил все необходимое. Торговли почти не было. Развитие торговли позволило получать ресурсы извне феода. Однако формирование торговых путей шло через хабы и центры, аккумулирующие у себя ресурсы. Очевидно, что такие центры не могли расти бесконечно, так возникали параллельные пути, но торговля не шла от всех ко всем.
Следовательно, можно сказать, что варп-двигатели и другие способы быстрого перемещения сформируют сеть перемещений с центрами, которые и будут фильтровать нужное, обладать предельной пропускной способностью и тем самым лимитировать объёмы, бесконечности в отдельной точке не сможет возникнуть, иначе это уже будет оружием, а не торговлей.
Но что, если технология позволит из любой точки пространства попасть в другую по щелчку? Пропускная способность точки никуда не денется. Даже несмотря на бесконечное количество точек, количество взаимодействий с ними будет равно константе, описанной выше, к тому же в этом случае нужно решить проблему того факта, что вам необходимо заранее знать о точке, с которой собираетесь взаимодействовать, а бесконечного знания так же не может быть. Данный вариант является наиболее хаотичным и скорее всего не возникнет с точки зрения организации, чем технологий. Разумеется, если окажется возможным вытворять такое с физическими законами.
Экономико-политические предпосылки и необходимость
Экономика не бывает без политики, политика неосуществима без экономики. Развитие человечества на протяжении истории строилось на основе централизации и укрупнения. Технологии управления людьми совершенствовались и изменялись. Одни формации сменялись другими, но вне зависимости от происходящего был неизменен в своей основе один вид деятельности, который и позволил развитие человечества - обмен.
Обмен существовал и в первобытном строе, с той лишь разницей, что обменивались друг с другом племена, а не отдельные индивидуумы. Форма обмена и обеспечение процесса менялись, но суть - никогда. Первые государства строились либо на организации обмена, либо транзите товаров. Для обеспечения процесса требовался учёт, а за ним сразу возник вопрос о достоверности и безопасности обмена. Товар может быть не того качества, которого должен быть. Да и получение товара покупателем может осуществляться различными способами, в том числе незаконными. Разумеется, сложно назвать обмен обменом, когда тебе предлагают не проламывать голову, а взамен передать что-то "благодетелю" или "бандиту". Однако на таком обмене строилась экономика феодализма. Это упрощённое описание и в прошлом все было несколько сложнее, но смысл тот же.
По мере развития политических и экономических систем, обмен стал не просто основой формирования государств, а способом управления другими государствами, например за счёт неэквивалентного обмена, принуждения торговать по установленным извне ценам. А на данный момент и прямого контроля за торговыми операциями путём арбитража. Сделка осуществляется с помощью банка, в котором обе стороны держат свои активы. Их можно в любой момент украсть под каким-то надуманным предлогом самыми разными способами - комиссия, конфискация, банкротство банка или обесценивание актива. Обмен остался, а способ "проломить голову" немного изменился, не так ли? Только теперь ещё добавилось в более жёсткой форме "Ты туда не ходи, ты сюда ходи, а то снег башка попадёт".
Социальные нужды и последствия внедрения
Основное требование общества - это всегда стабильность. Проистекает такое требования из сущности общественных объединений, при условии, что они не являются террористическими или асоциальными. Основная проблема общества и государства - это легитимность и невозможность манипуляции историей. Учитывая, что внедрение фиатных денег не привело к краху государств, то можно точно сказать, что перевод экономических операций в цифровую область не приведёт к катастрофе. Разумеется пожилым людям будет сложно воспринимать такие изменения, но при планомерном внедрении и позитивном мотивировании возможно закончить этот процесс за десять-двадцать лет.
Следует отметить, что в данный момент превалирующая экономическая теория предусматривает только капитализм, а именно либерализм в его самой анти-человечной форме. Внедрение цифровой системы для обработки платежей будет ускорять процессы разрушения такой экономики и либо сломается шея, либо система.
Основная проблема заключается в том, что ограбление масс населения приведёт к снижению способностей масс, деградации. Рано или поздно это приведёт к тяжёлым катастрофам, "охоте на ведьм" и т.д. Внедрение систем, хранящей почти все, что есть у человека - опасно в первую очередь для капиталиста, несмотря на получение самого убийственного рычага влияния на массы.
2 Составление списка требований
Исходя из анализа в предыдущей секции, можно составить список требований, которым должна удовлетворять любая платёжная система на данный момент. А именно:
Количество активных пользователей и производимых транзакций за единицу времени неограниченно;
Осуществление бартерных операций;
Отсутствие возможности переписать историю транзакций, при соблюдении определённых условий (очевидно, что гарантировать полную невозможность переписывать историю транзакций невозможно, однако можно достичь таких условий, при которых изменения станут невозможными, либо стоимость осуществления таких изменений превысит любую возможную выгоду);
Линейный рост стоимости обслуживания всей системы от числа активных пользователей/транзакций в сутки;
Участие нескольких сторон в транзакции (несколько продавцов и/или покупателей);
Возможность арбитража третьей независимой стороной проводимых транзакций с целью подтверждения подлинности;
Возможность ограничивать передачу денежных средств, товаров или услуг с использованием фильтров (Имманентные ограничения для средств, товаров или услуг, сохраняемых при передаче третьему лицу): получатель (различными способами, такими как прямое указание счета, тега счета или пользователя, владеющего счётом, типом пользователя - физическое или юридическое, сфера деятельности и т.д.), отправитель (аналогично предыдущему пункту), на какие товары, услуги или средства можно обменять, время доступности/недоступности (например использование возможно начиная с 1 августа, и/или после 30 августа использование станет невозможным - средства будут списаны со счета), размер транзакции (стоимость товара должна быть в определённом диапазоне и т.д.);
Возможность ограничивать лицу передачу конкретных денежных средств, товаров или услуг с использованием фильтров, приведённых выше (ограничения будут аннулированы при передаче третьему лицу);
Интеграция в налоговую систему и автоматический расчёт налоговых отчислений;
Интеграция с клиринговой системой для упрощения расчёта статистики и балансов (межгосударственных, межобластных и т.д.).
3 Проект системы
Прежде чем приступить к проектированию системы необходимо уточнить важные моменты, а именно:
На данный момент существует множество государств и банков, оперирующие согласно собственному распорядку, соответственно невозможно предоставить решение, удовлетворяющее всех;
Автор не обладает достаточными познаниями в экономике, финансах и политике, что бы предоставить готовое решение, которое можно было бы внедрять.
Исходя из данных причин, следует рассматривать данный проект как пример, который можно использовать для проектирования готовой системы.
В дальнейшем следует понимать термин "субъект" исключительно как государство, однако субъектом может выступать банк или юр.лицо, которое по сути будет являться банком. Субъектом все они являются по той причине, что несут ответственность за совершаемые на узлах транзакции. Лицо, использующее услуги платёжной системы не может обладать субъектностью, т.к. несёт ответственность перед субъектом, а не платёжной системой в целом.
3.1 Верхне-уровневая структура системы
Для начала рассмотрим архитектуру системы отдельного субъекта на рисунке ниже. Данная система является базовым элементом всей платёжной системы.
Система состоит из следующих подсистем:
Управление - управляет ресурсами системы, аутентифицирует пользователей и позволяет им создавать счета (кошельки) на узлах;
Узел - предоставляет доступ пользователям к их счетам(кошелькам), проводит транзакции;
Клиринг - осуществление учёта проведённых транзакций, возможно организация в виде дерева для сокращения передаваемых данных и распределения нагрузки. Если количество узлов мало, то субъекту достаточно одного такого сервиса;
Налоговая - налоговый сервис субъекта;
Шлюз событий - позволяет узлам обмениваться сообщениями друг с другом, в случае подключения к глобальному шлюзу - передаёт/получает глобальные события, по отношению к субъекту.
Теперь, имея архитектуру каждого субъекта, можно построить глобальную систему (или контур субъектов), изображённую на рисунке ниже. Данную архитектуру можно масштабировать бесконечно, если принять во внимание возможность объединять контура в контур контуров. Однако скорее всего невозможность столь больших людских объединений, ввиду недостаточности развития политических технологий, окажется большим лимитирующим фактором, нежели технологические проблемы объединения.
Благодаря такой архитектуре системы государства могут осуществлять прозрачное объединение своих экономик в один контур. Преимуществом такого подхода является возможность предоставление инфраструктуры по обмену сообщениями как внутри контура, так и вне его, если таковые связи будут налажены (между контурами). При этом каждый субъект сохраняет возможность прямого общения с любым узлом вне контура, для этого необходимо объединить шлюз событий с другим субъектом или общим центром других субъектов. Субъект может входить в множество контуров, т.к. шлюз событий выполняет исключительно роль маршрутизации событий соответствующему субъекту.
За счёт иерархии шлюзов сообщений можно выстроить такую маршрутизацию, при которой узел из системы субъекта1 может не знать о существовании системы субъекта2, т.к. определение маршрута делегируется шлюзу.
Общий клиринг позволяет анализировать проводимые внутри контура операции и строить общую статистику на основе поставляемых субъектами данных. Подробнее про клиринговую систему будет описано позднее.
3.2 Подсистема субъекта: Управление
Управление хранит информацию о пользователях, их ресурсах на узлах. Проводит аутентификацию и предоставляет информацию пользователю для выполнения запросов на узлы. Управление может выступать в качестве сборщика информации с узлов для выдачи агрегированных данных клиенту пользователя, что является актуальным для мобильных клиентов.
Т.к. на текущий момент технология построения подобных систем хорошо развита, то подробное описание не требуется. Разрабатываться данная подсистема не будет.
3.3 Подсистема субъекта: Узлы
Узел является главной частью платёжной системы, он хранит текущую информацию о счёте, проводит операции и авторизует доступ пользователей к счетам. В дальнейшем счета в подсистеме узел будут называться кошельками.
Прежде чем приступать к архитектуре узла необходимо задать определения.
Контракт - набор операций, выполняемых с кошельками;
Кошелёк - совокупность доступных ресурсов;
Баланс - денежные ресурсы кошелька;
История контрактов - завершённые контракты кошелька.
На рисунке выше изображена структура узла. Платёжная система субъекта состоит из таких узлов, количество определяется количеством кошельков, которые необходимо обрабатывать, и количеством операций с их участием. Условно узел можно разделить на несколько базовых сервисов, без которых он не может существовать:
Баланс;
Контракты;
История контрактов;
Кошельки;
(Опционален) Шаблоны налогов;
(Опционален) Адаптер клиринга.
Существует возможность добавление иных сервисов, о них будет рассказано позже. Рассмотрим каждый из сервисов отдельно.
3.3.1 Баланс
Данный сервис отвечает за работу с денежными средствами. Все операции совершаются при помощи распределенных транзакций, а именно: в начале средства резервируются (или фиксируется возможность пополнения), затем сервис ожидает команды завершения или отмены транзакции. Для этого существует универсальный протокол. Денежные средства в рамках протокола называются ресурсом.
Посредством очереди сообщений сервис получает одну из трёх типов команд для работы с транзакциями:
Создать;
Подтвердить;
Отменить.
На каждую команду сервис отвечает одним из вариантов ответа:
Статус транзакции, команда завершилась штатно;
Ошибка - выполнение команды невозможно и описание ошибки.
Повторная отмена или подтверждение транзакции приводит к тому же результату, что в первый раз, если отправлять команду подряд. Нельзя отменить подтверждённую транзакцию или подтвердить отменённую.
Сервис работает со следующими сущностями:
Валюта - идентифицирует валюту по ее коду;
Баланс - текущее количество валюты в кошельке с учётом ограничений;
Транзакция - информация о совершаемых с балансом операциях.
Схему и отношения сущностей можно увидеть на рисунке ниже.
Каждому кошельку может принадлежать множество записей сущности баланс. Если у валютных средств нет ограничений, то запись будет только одна. В случае наличия ограничений на каждый вариант будет создаваться своя сущность (например можно заплатить только за определённую категорию товаров, наличие меток или ограничений на временной интервал).
Рассмотрим работу с данной сущностью на примере. На кошелёк необходимо зачислить 100 рублей, 1000 рублей с возможностью покупки предметов первой необходимости, а так же 1000 рублей с меткой "метка". В этом случае кошельку будет принадлежать 3 записи Баланса, согласно которых: 100 обычных рублей и 2000 рублей меченных - половина ограничением платежей, вторая содержать метку. Если в дальнейшем нужно будет зачислить ещё 200 рублей, то они увеличат первую запись, созданную для 100 рублей, до 300 рублей.
При снятии средств с кошелька необходимо найти наиболее подходящий баланс под условия снятия средств. Так, если владелец будет покупать предмет первой необходимости, то требуется списать средства с максимально соответствующей записи. Если средств не хватает, то брать следующий, наиболее подходящий баланс. Данный процесс повторяется пока записи не закончатся, либо не будет списано нужное количество средств.
На каждый баланс ссылаются транзакции - журнал совершенных или совершаемых с балансом операций. У каждой транзакции есть статус, по которому можно отследить прогресс.
3.3.2 Контракты
Сервис контрактов является ядром обработки платежей всей системы. Базовой сущностью, с которой работает сервис, является контракт. Контракт в простейшем случае является списком перемещений ресурсов между кошельками. Ресурсом может выступать всё, что может быть отчуждено от одного лица и передано другому, например: деньги, долговые обязательства, права аренды или владения, услуги и товары.
Контракты могут быть простыми, если требуется передать ресурс, и сложными, если необходимо наступление каких-либо событий, действий, выполнение условий. Простые контракты выполняются сразу после запуска и либо заканчиваются успехом, либо отменяются. Сложные контракты могут подразумевать этапы обработки, невозвращаемые передачи или иные условия. Сложные контракты в данной работе рассматриваться не будут. Дальнейшее описание будет исходить из работы с простыми контрактами.
Контракт может исполняться на нескольких узлах одновременно. Это требует решения проблемы распределенных вычислений и соблюдения условий контрактов. События могут происходить в разное время и, учитывая специфику важности осуществляемых операций, консенсус может быть достигнут только в случае идентичности полученных данных на всех узлах.
Для обеспечения такой обработки контрактов (как простых, так и сложных), необходима такая модель данных, которая позволит на каждом из участвующих в контракте узлов принять решение о том, является ли контракт исполненным или нет.
На рисунке выше изображены сущности, с которыми работает сервис контрактов. Сущность контракта определяет способ обработки (схема), а так же основную информацию по контракту необходимую для его идентификации и корректной обработки.
Основная задача по хранению возникающих событий контракта ложится на сущность фактов контракта. Каждый факт определяется его деталями в виде JSON-документа, схема которого задаётся контрактом. Такая модель данных позволяет осуществить распределенную обработку. Несмотря на то, что каждый узел самостоятельно принимает решение о состоянии контракта - один из узлов является мастером контракта и всегда принимает окончательное решение на основании полученных от других узлов фактов. Если дочерний узел принимает решение, что контракт выполнен, все условия достигнуты, то он сообщает мастеру о своём решении. Мастер может считать контракт завершённым только в том случае, если все дочерние узлы сообщили такое решение и на мастере достигнуты все условия завершения. Для отмены контракта любой узел может отправить факт отмены, который получат все узлы, но мастер примет решение об отмене только в том случае, если контракт ещё не завершён. Очевидно, что если узел, отправивший отмену, не отправлял подтверждение, то контракт не может оказаться завершённым. Работа с контрактами всегда строится на основе графа состояний.
Так как логика обработки задаётся схемой, то в случае необходимости изменить логику работы или добавить новый функционал, не нужно переделывать инфрастуктуру передачи/хранения. Наладка такой инфраструктуры осуществляется один раз, и в дальнейшем, для добавления новых функций или расширения возможностей нужно добавлять/изменять схемы контрактов.
Такой подход позволит каждому субъекту настраивать обработку платежей таким образом, каким ему нужно. Очевидно, что для работы с другими узлами, потребуется поддержка таких контрактов.
Защита контракта от подлога
Когда узел отправляет всем остальным созданный им факт, то узел гарантирует его подлинность. В случае подлога отправленного факта за действия узла несёт ответственность субъект, владеющий узлом.
Минимальная схема контракта
Как было описано выше, для того, что бы контракты работали - необходима реализация схемы обработки фактов контракта. Рассмотрим минимально возможную схему, при которой можно передать ресурсы между 2 и более кошельками.
Такая схема должна содержать минимальный набор типов фактов, отправляемых между узлами, приведём их все:
Добавление кошелька - создаётся на каждый кошелёк контракта;
Добавление информации о кошельке - каждый узел должен создать такой факт на каждый принадлежащий ему кошелёк. Данный факт необходим для удостоверения личности владельца кошелька остальными сторонами. Если по каким-то причинам лицо будет нежелательным для субъекта - это позволит отказать в проведении транзакций;
Передача ресурса - данный факт фиксирует, что необходимо передать определённый ресурс от кошельков-источников кошелькам-назначениям, данный факт является информационным, никакие ресурсы не передаются;
Резервирование ресурса - фиксирует факт блокировки ресурса кошелька (или заморозки пополненного), согласно передачам, содержит информацию о фактическом ресурсе, который был заблокирован, а не ресурса, требуемого для передачи. Количество может так же отличаться;
Ошибка обработки - фиксирует невозможность обработать контракт узлом, например из-за нехватки ресурса у кошелька или иной, в том числе программной, ошибки;
Статус контракта - узел сообщает текущий статус контракта согласно доступных ему фактов, минимальное количество включает в себя: инициализация - подготовка и проверка контракта, активность - блокировка ресурсов и ожидание блокировок от остальных узлов, создание замороженных пополнений ресурсов согласно полученным блокировкам от других узлов, завершение - узел готов завершить контракт и списать заблокированные средства, начислить замороженные, завершён - узел списал заблокированные средства и начислил замороженные, отменён - узел требует отмены контракта, возврат заблокированных средств и отмена замороженных пополнений произойдёт только после отправки такого статуса мастером.
Схема построена исходя из гарантии инфраструктурой доставки сообщений без потерь (сообщение доставлено хотя бы один раз до каждого узла).
Варианты ресурсов и способы работы с ними
Платёжная система не ограничивается работой с деньгами. Ввиду универсальности протокола существует возможность поддержать следующие типы ресурсов:
Долговые обязательства - данный ресурс предполагает, что в результате обмена вторая сторона получит обязательства по уплате долга от первой. Для этого необходима разработка сервиса долгов, ведущих учёт и корректную обработку типа долга (кредит, облигация, депозит и т.д.);
Товар, услуга - данный тип ресурса предполагает передачу материальных и нематериальных средств второй стороне, при этом существует возможность поддержки обновления инвентаря, проверки подлинности (авторские права на использование, разрешение на дистрибуцию т.д.). При реализации специальных реестров для товаров и услуг возможно так же отслеживание подлинности товара (что продавец действительно владеет товаром);
Недвижимость - при разработке специального реестра недвижимости и адаптера к нему в платёжной системе, возможно реализовать передачу прав собственности, сдачу в аренду и др. Система автоматически предложит оплатить аренду или запустит процедуру расторжения аренды после получения подтверждения от собственника (в т.ч. авто-подтверждение).
Данный список не ограничивается только этими ресурсами, возможны и другие варианты, если для них существует возможность отчуждения, как полное, так и частичное.
3.3.3 История контрактов
Сервис истории контрактов выполняет следующие функции:
Хранит информацию о контрактах (завершённых и активных, но не черновиков);
Ограничивает доступ пользователей к контрактам согласно участвовавшим кошелькам узла.
Когда сервис контрактов генерирует события создания, завершения или отмены контракта - сервис истории контрактов осуществляет регистрацию или изменение сохранённой у себя копии. Доступ к контракту получают все кошельки узла, зарегистрированные в контракте. Пользователь получает доступ к контракту, если у него есть соответствующий доступ к кошельку.
Если контракт не находится в терминальном статусе (завершён или отменен), тогда сервис получает текущую копию состояния контракта из сервиса контрактов и кеширует ее у себя.
Выдача информации о контракте пользователю осуществляется согласно условиям отображения контракта. Часть информации может быть не видна всем пользователям. Если в контракте участвовало две стороны, то каждый пользователь видит всю информацию.
Защита истории контрактов от изменений
Для обеспечения защиты истории контрактов необходимо добавить функционал, который позволит добавляемым завершённым контрактам получать специальную метку и привязывать его к предыдущему.
В простейшем варианте алгоритм будет выглядеть следующим образом, следует отметить, что алгоритм работает в многопоточной среде:
Получить метку предыдущего контракта;
Вычислить метку нового исходя из состояния контракта и метки прошлого контракта, вычислить дополнительно контрольную сумму контракта (несмотря на то, что метка де-факто содержит в себе контрольную сумму). Данная метка является теперь меткой последнего контракта;
Отправить всем узлам, участвовавшим в контракте, метку и контрольную сумму и получить от них метки и контрольные суммы;
Сформировать документ, содержащий все метки контракта на разных узлах и контрольные суммы, а так же добавить метку последнего на данный момент зарегистрированного контракта в системе (не обязательно тот же самый контракт ) и вычислить его метку. Теперь эта метка является меткой последнего контракта.
Для обеспечения производительности рекомендуется держать несколько меток последнего контракта и брать любую для регистрации нового.
Для синхронизации меток можно использовать специализированную схему контрактов, что бы переиспользовать существующую инфрастуктуру.
Легко заметить, что для обеспечения невозможности подмены достаточно, что бы в системе проводилось большое количество контрактов, такое при котором современное оборудование не сможет вычислить метки без длительного отключения серверов. Так же следует взять во внимание необходимость синхронизировать такие метки со всеми остальными узлами.
В случае, если в платёжной системе будет не менее двух субъектов, а контракты между ними осуществляются регулярно, то подделка контрактов станет нереализуемой, за исключением случаев сговора. Однако техническая трудность такой операции никуда не денется и встанет более остро. Даже квантовые вычисления не спасут ситуацию ввиду перекрёстной зависимости меток на узлах друг от друга.
Если система будет масштабирована бесконечно, то подделка контрактов будет невозможна по причинам нарушения законов физики, что бы такое выполнить.
Таким образом отсутствие отключений и регулярные проверки меток (см. параграф ниже) обеспечат гарантию подлинности истории контрактов.
Вотум недоверия
При такой организации истории контрактов и соответствующих договорённостях между субъектами, возможно вынесения вотума недоверия к истории контрактов узла или всех узлов субъекта/контура субъектов.
Очевидно, что такую ситуацию необходимо решать, желательно без останова работы системы. Для этого предполагается создание центра, в который можно передать историю контрактов от узлов. Под присмотром наблюдателей от всех субъектов, участвующих в данной процедуре, проводится проверка соответствия меток и контрольных сумм. В отдельном случае возможно проведение семантической сверки контрактов (т.к. сравнить состояние эквивалентностью невозможно ввиду как минимум различия часовых поясов), для выявления различий и обнаружения ошибок или подлогов.
3.3.4 Кошельки
Сервис кошельков хранит информацию:
Существующих кошельках и их статусе;
Правах доступа пользователей к кошелькам;
Документы ответственного за кошелёк лица (паспорт, ИНН и др.).
Сервис кошельков получает команды от сервиса управление на создание кошелька, изменение доступа пользователя, изменение документов ответственного лица.
В случае миграции узла от одного сервиса управления к другому все доступы к кошелькам будут заблокированы, процесс восстановления доступа в новом сервисе управления осуществляется на основании документов, подтверждающих права владения данным кошельком (ответственное лицо).
3.3.5 Шаблоны налогов
Сервис отвечает за получение, хранение и генерацию налоговых списаний при создании контракта.
3.3.6 Адаптер клиринга
Данный сервис слушает событие завершения контракта, конвертирует финальное состояние контракта в модель клиринга для последующей регистрации во всех подключённых к сервисах.
3.4 Подсистема субъекта: Налоговая
Налоговая подсистема хранит информацию о налогах, взимаемых с операций согласно определенным требованиям.
В простейшем варианта данная система хранит списки, позволяющие определить, какие именно транзакции подлежат налогообложению.
Каждый элемент списка описывается в виде шаблона, например: для сумм, превышающих значение Х, за товары, входящие в категорию К, необходимо выплатить M%, где X, K, M могут быть как константами, так и определяться исходя из:
Статистики товара, пользователя или другого источника;
Настроек узла;
Реестра.
Приведём пример как данный шаблон может быть развернут:
Значение X определяется исходя средней стоимости товара в клиринге;
K задаётся реестром товаров, определяющим какие именно товары входят в категорию;
Процент M определяется по прогрессивной шкале для покупателя (лица, не счета) в зависимости от уже уплаченного налога/налогооблагаемой базы по товару/категории/общей.
На рисунке выше отображён пример архитектуры налоговой, согласно тем функциям, которые требуются для платёжной системы, а именно:
Аккумуляторов значений - хранит информацию о налогооблагаемой базе, уплаченных налогах с разбиением на различные критерии, например по специальному коду, категории товара, типу услуги и др.
Хранилище налоговых шаблонов - хранит и сообщает об изменениях узлам платёжной системы субъекта.
Платёжную систему можно реализовать без интеграции с налоговой системой, но делать этого не нужно. Возможность автоматически рассчитывать налоги и видеть информацию об уплаченных налогах/задолженностях является большим удобством.
3.5 Подсистема субъекта: Клиринг
Как уже выше было сказано клиринг строится на основе древовидной структуры. Не имеет смысла передавать выше данные, которые требуются только для учёта внутри обслуживаемой области. Например клирингу субъекта не нужно отправлять операции внутри субъекта вовне. Исходя из этого можно составить простейшую архитектуру клиринга.
На рисунке выше отображена структура работы подсистемы клиринга. Шлюз данных от источников принимает данные от узлов и таких же подсистем клиринга, как текущая. Далее полученные данные должны быть сохранены в БД. Если в операции участвует хотя бы один внешний счёт, по отношению к настройкам клиринга, тогда полученный пакет данных должен быть в неизменном виде передан вышестоящей подсистеме клиринга. Таким образом достигается фильтрация данных на каждом уровне и предоставление выше только актуальных для заданного масштаба данных.
Ввиду высокой нагрузки на подсистему необходимо разделить обработку и сохранение с вычислительными операциями. Этого можно достичь путём добавления реплики БД для чтения, с которой аналитики и аналитические сервисы будут работать.
3.6 Подсистема субъекта: Шлюз событий
Шлюз событий является шиной данных, от которой требуется соблюдение одной гарантии - гарантированная доставка сообщения. Время доставки не зависит от архитектуры и ложится полностью на сопровождение.
4 Разработка эскизного проекта
Все исходники можно найти в группе гитлаба.
Для того, что бы изучать систему не обязательно поднимать полноценное дев окружение, однако потребуется вручную создавать образы БД в локальном докере.
4.1 Подготовка среды разработки (DEV-окружение)
Прежде чем приступить к разработке необходимо подготовить kubernetes кластер для работы, а так же обладать следующими сервисами, доступными с вашего ПК:
CertManager + TrustManager c настроенным сертификатом и генератором сертификатов, корневой сертификат регистрируем в нашей системе;
Postgresql, настроить следующие БД:
одинаковые node_0000, node_0001, но с разными пользователями, создать схемы:
contract;
contract_history;
balance;
tax_matcher;
wallet;
clearing в ней схему clearing_core;
Keycloak - добавить реалм mireapay.dev, с этим сервисом будут работать сервисы rest-gateway узлов;
Sonatype Nexus (не используйте репозитории gitlab, они ужасны по всем параметрам) - для хранения артефактов и docker-образов приложений;
Gitlab - для хранения кода, запуска скриптов сборки и инициализации процесса раскатки/обновления;
ArgoCD - для раскатки и обновления сервисов;
Apache Pulsar, добавить тинанты:
одинаковые node_0000, node_0001, у каждой пространства имён balance, contract;
clearing и пространство имен core.
В данной работе ожидается, что читатель умеет работать с данным ПО и в дополнительных комментариях не нуждается.
Настроить все эти сервисы можно локально на одной машине, но тогда потребуется 16 ядер и 128 ГБ ОЗУ минимум. Уместиться в 64 ГБ ОЗУ можно, но разработка будет на пороховой бочке, т.к. останется свободным после системы и Intellij Idea c десятком вкладок в хроме от силы 2-4 ГБ. Либо отказываться от второго узла, тогда 64 ГБ ОЗУ достаточно.
4.1.1 Настройка Sonatype Nexus
Убедитесь, что у вас настроены 2 репозитория в формате maven2 - maven-releases, maven-snapshots и создан docker-реестр. Если вы не желаете редактировать имена или не знаете как переадресовывать домена, то sonatype nexus должен быть доступен по адресу nexus.dev, а реестр докер контейнеров - docker.nexus.dev.
4.1.2 Настройка Gitlab
Для того, что бы проекты собирались корректно, необходимо доработать раннеры:
[[runners]]
tls-ca-file = "/home/gitlab-runner/.gitlab-runner/certs/tls.crt"
pre_build_script = """
# Certificate registration
if [ -f /__cacert_entrypoint.sh ]; then
echo "Adding CA certificates (Gradle)"
mkdir -p /certificates
cp /certs/tls.crt /certificates/ca.crt
export USE_SYSTEM_CA_CERTS=true
/__cacert_entrypoint.sh
else
echo "Adding CA certificates (Default linux)"
cp /certs/tls.crt /usr/local/share/ca-certificates/ca.crt
update-ca-certificates --fresh > /dev/null
fi
"""
[runners.kubernetes]
allowed_pull_policies = ["if-not-present", "always"]
pull_policy = ["always", "if-not-present"]
[[runners.kubernetes.volumes.secret]]
name = "ca-bundle"
mount_path = "/certs"
read_only = true
{{- if .Values.global.minio.enabled }}
[runners.cache]
Type = "s3"
Path = "gitlab-runner"
Shared = true
[runners.cache.s3]
ServerAddress = {{ include "gitlab-runner.
cache-tpl.s3ServerAddress" . }}
BucketName = "runner-cache"
Insecure = false
{{ end }}
ca-bundle - это секрет, в котором содержится корневой сертификат, его можно как генерировать автоматически в пространство имен в kubernetes, так и создать руками.
Теперь необходимо создать группу mireapay, и добавить проект mireapay/devops/pipeline. В репозитории есть папка agent, в ней скрипт сборки агента, собираем и пушим в docker.nexus.dev/lib/ci/agent:1.0.
Для всей группы mireapay добавляем следующие переменные:
CI_BOT_NEXUS_PASSWORD - пароль пользователя nexus;
CI_BOT_NEXUS_USERNAME;
CI_BOT_TOKEN_KEY - токен доступа для бота к репозиториям в группе mireapay;
CI_BOT_TOKEN_NAME;
DB_CLEARING_DB - имя базы данных, не url, если потребуется поменять сервер, то смотри файл gradle-liquibase-deploy.yml в проекте mireapay/devops/pipeline;
DB_CLEARING_PASSWORD;
DB_CLEARING_USER;
DB_NODE_0000_DB - имя базы данных, не url;
DB_NODE_0000_PASSWORD;
DB_NODE_0000_USER;
DB_NODE_0001_DB - имя базы данных, не url;
DB_NODE_0001_PASSWORD;
DB_NODE_0001_USER.
Добавляем и собираем проект mireapay/common/gradle-plugin/database/.Все проекты */database-liquibase можно добавлять в gitlab и раскатать в postgres.
Импортируем, ждем сборки и создания тэга, после чего раскатываем последний тэг на БД. Если креды к БД у вас с флагом protected, то не забудьте в репозиториях выставить все тэги *-RELEASE как protected.
Конфигурации для argo лежат в папке mireapay/devops/argo, нужно добавить репозитории в gitlab.
После этого импортируем остальные проекты, однако нужно собирать в следующем порядке:
mireapay/common/versions;
mireapay/common/toolkit;
mireapay/common/test;
mireapay/common/model;
mireapay/common/message-queue;
mireapay/common/cache;
mireapay/common/gradle-plugin/api/;
mireapay/common/payment-resource/;
mireapay/node/model/internal/;
mireapay/node/model/event/;
mireapay/node/model/dto/;
mireapay/node/api/rest/;
mireapay/clearing/model/internal/;
mireapay/clearing/model/event/;
mireapay/clearing/model/dto/.
Остальные проекты зависят от вышеперечисленных проектов и могут собираться в любой последовательности.
4.1.3 Настройка ArgoCD
Создаем 3 проекта mireapay-node-0000, mireapay-node-0001, mireapay-clearing. Для них создаём соответствующие пространства имён в kubernetes. Репозитории для проектов лежат в mireapay/devops/argo.
Дожидаемся запуска всех проектов и проверяем работу. Для этого потребуется в БД завести информацию о кошельках, в БД баланса добавить денег на кошельки.
Теперь выполняем запрос, предварительно получив JWT-токен из keycloak, название куки X-JWT-TOKEN, для заголовка по стандарт http:
POST {{MPS_REST_API_URL}}/rest/api/v1/contract/default/quick-transfer
X-Wallet-Id: 1
{
"targetWallet": "0000@2",
"resource":{
"type": "PAYMENT_RESOURCE_CURRENCY",
"currency": "RUB",
"amount": 1
}
}
И наблюдаем, как сыпятся ошибки в логах. Но если все настроено правильно, то в БД контрактов будет завершенный контракт, а в балансе для кошельков будут видны проведённые операции.
4.2 Описание проектов
Все проекты строятся по примерно одинаковой логике: корневые библиотеки - модели - компоненты сервисов.
К корневым библиотекам относятся:
mireapay/common/versions - задаёт версии всех используемых библиотек в проектах. В любом репозитории, кроме данного, указание версий внешних зависимостей запрещено;
mireapay/common/toolkit - набор полезных инструментов при разработке;
mireapay/common/test - библиотека для тестирования, содержит авто-конфигураторы для тестирования БД и пульсара;
mireapay/common/model - общие модели для любых сервисов;
mireapay/common/message-queue - библиотека для работы с очередями, включая тестирование без необходимости поднимать внешние зависимости;
mireapay/common/cache - работа с кешем, поддерживается только redis;
mireapay/common/gradle-plugin/api/ - плагин, позволяющий генерировать из декларации контроллера сразу клиент и gateway;
mireapay/common/payment-resource/ - ресурсы платежей, выделены в отдельный пакет, т.к. они должны быть стандартом и не смешиваться с другими изменениями.
К моделям относятся проекты:
mireapay/node/model/internal/
mireapay/node/model/event/
mireapay/node/model/dto/
mireapay/clearing/model/internal/
mireapay/clearing/model/event/
mireapay/clearing/model/dto/
Внутренние модели используются непосредственно внутри сервисов, в события кладутся объекты согласно внутренней модели. DTO используются только для выдачи вовне.
Отдельно следует выделить проект mireapay/node/api/rest/, задающий RestAPI (как внутреннее, так и внешнее) сервисов узлов.
Все сервисы системы состоят компонент с типами:
processor - обработка событий из очереди сообщений, сервис не должен обрабатывать http-запросы;
api - обработка http-запросов, может отправлять в очередь сообщений, но не читать;
adapter - специализированный сервис по интеграции с внешним сервисом, работает только на отправку;
gate - тоже самое, что и adapter, но может принимать запросы извне.
Все компоненты можно тестировать локально, достаточно либо иметь доступ к развёрнутой БД соответствующего сервис, если есть, либо докер-контейнер с соответствующей БД. Для создания докер контейнера не обязательно разворачивать гитлаб и нексус. В тестовом конфиге компоненты в файле src/test/resources/application-test.yml можно указать свой образ БД.
testcontainers:
db:
image: "docker.nexus.dev/mireapay/node/balance/database:dev"
Либо запускать тесты с переменными окружения:
TESTCONTAINERS_DB_ENABLED: "false"
SPRING_R2DBC_URL: "r2dbc:postgresql://test-database:${POSTGRES_PORT}/${POSTGRES_DB}?currentSchema=${POSTGRES_SCHEMA}&sslmode=disable&binary_parameters=yes"
SPRING_R2DBC_USERNAME: "${POSTGRES_USER}"
SPRING_R2DBC_PASSWORD: "${POSTGRES_PASSWORD}"
Во втором случае будет создаваться подключение к вашей БД. По умолчанию тесты разрабатываются так, что бы по завершении все создаваемые данные удалялись.
Для тестирования работы с очередями сообщений пульсар не требуется, никакие контейнеры не поднимаются, вся работа с очередями эмулируется на уровне библиотеки message-queue-test.
4.3 Сервис баланс
Данный сервис состоит из компонент: mireapay/node/balance/processor/ и mireapay/node/balance/api/.
База данных в проекте mireapay/node/balance/database-liquibase/ содержит схему, а так же небольшое описание по работе.
Сгенерировать балансы для кошельков можно запросом:
INSERT INTO balance.balance_tab(part, wallet_id, currency_id, amount, details)
SELECT 2024,
wallet_id,
1,
1000000,
null
FROM generate_series(100000, 200000) AS wallet_id;
В результате для кошельков с 100000 по 200000 будет зачислено 1000000 рублей (по умолчанию валюта RUB имеет идентификатор 1).
Таблица balance_tab устроена таким образом, что бы ее можно было ротировать со временем с помощью партиций. Работает это следующим образом - в самом начале у нас имеется только 2 партиции: текущий год и прошлый год в минус бесконечность. Вторая партиция нужна для аккумулирования долгоживущих записей прошлых лет. Процедура выполняется процедурой \verb|balance_tab_obsolete_partition|, она создаёт новую партицию из активных записей удаляемой партиции и всех активных записей минус бесконечности. Новая партиция заменяет две старых. Таким образом можно подчищать устаревшие паритции, на которых уже большинство записей удалены.
Такая процедура необходима, т.к. postgresql не будет эффективно работать с большим количеством партиций, рекомендуется не превышать 5-7 штук в зависимости от производительности вашего сервера и дисковой подсистемы.
До того, как начнётся новый год, необходимо вызывать процедуру добавления партиции balance_tab_add_partition. Несмотря на то, что в статье указывается год, как окно для действия партиции, можно указать любое окно - 10 лет, 100 лет или месяц.
Сервис balance-processor - обслуживает сервис контрактов в части создания и проведения распределенных транзакций с ресурсами по универсальному протоколу. Входной точной обработки сообщений из очереди является класс com.lastrix.mps.node.balance.processor.payment.PaymentEventConsumer. Согласно библиотеке mireapay/common/message-queue от данного обработчика требуется вернуть ответ о подтверждении обработки, отрицательный ответ или пустой ответ, означающий подтверждение обработки сообщения.
4.3.1 Обработка создания транзакции
Если тип входного сообщения является созданием транзакции, тогда вызывается обработчик com.lastrix.mps.node.balance.processor.payment.processors.CreatePaymentEventProcessor. Его работу можно описать простым алгоритмом:
Транзакции для полученного идентификатора платежа существуют?
Да, вернуть существующие в БД транзакции по идентификатору платежа в качестве успешной обработки;
Нет, продолжить выполнение.
Сгенерировать транзакции;
Получить изменения для балансов;
Создать несуществующие балансы (например, если их не существует для нужной валюты);
Сохранить транзакции;
Вернуть созданные транзакции в качестве успешного результата обработки.
Все транзакции работают только с одной записью баланса, транзакция не может ссылаться на множество балансов. Следует отметить, что все транзакции делятся на 2 типа: пополнение и снятие.
Транзакции пополнения создаются таким образом, что деньги не отображаются на балансе, а замораживаются в транзакции. Если баланса не существовало, то он будет создан с 0 значением.
Транзакции снятия при создании изменяют баланс путём уменьшения на сумму указанную в транзакции.
4.3.2 Обработка отмены транзакции
Обработку отмены осуществляет обработчик com.lastrix.mps.node.balance.processor.payment.processors.CancelPaymentEventProcessor. Если транзакция уже отменена, то результатом может быть только успешная обработка со списком отменённых транзакций. Нельзя отменить завершённые транзакции.
Если для полученного в сообщении идентификатора платежа есть активные транзакции, тогда в зависимости от типа транзакции обработчик выполняет одно из действий:
Пополнение - переводит транзакцию в статус отменено, не изменяет баланс;
Снятие - возвращает средства на баланс, переводит транзакцию в отменённое состояние.
При успешном завершении обработки всегда возвращается список транзакций ассоциированных с идентификатором платежа.
4.3.3 Обработка завершения транзакции
Обработку завершения осуществляет обработчик com.lastrix.mps.node.balance.processor.payment.processors.ConfirmPaymentEventProcessor. Аналогично отмене, данный обработчик всегда отвечает успешно, если транзакции завершены. Нельзя завершить отменённые транзакции.
Если для полученного в сообщении идентификатора платежа есть активные транзакции, тогда в зависимости от типа транзакции обработчик выполняет одно из действий:
Пополнение - зачисляет средства на баланс, переводит транзакцию в статус завершено;
Снятие - переводит транзакцию в завершённое состояние.
При успешном завершении обработки всегда возвращается список транзакций ассоциированных с идентификатором платежа.
4.4 Сервис контрактов
Данный сервис состоит из компонент: mireapay/node/contract/processor/ и mireapay/node/contract/api/.
В проекте mireapay/node/contract/database-liquibase/ можно найти схему контрактов. Все контракты устроены таким образом, что в таблице contract_tab содержится только мастер-запись с важными настройками, необходимыми для корректного запуска обработки фактов контракта, хранящихся в contract_fact_tab.
Так же как в БД баланса, все контракты разделены через партицированние. Окном партицирования является 1 день, предполагаемый объем партиции за день должен достигать ~30 гб для 100 млн платежей в день. Т.к. после завершения контракта любым способом обращение к записям уже не требуется, то такие партиции можно смело удалять из БД. Данные настройки предусматривают, что все контракты простые и выполняются в течении нескольких минут, например полчаса на оплату.
Если требуется обработка сложных, долгих контрактов, рекомендуется сделать отдельный сервис сложных контрактов, который будет работать со своей БД, но инфраструктура будет примерно такой же (ее потребуется доработать, чтобы мультиплексировать сообщения, приходящие извне).
Доступ пользователей к информации в БД не осуществляется. Получение записей должно осуществляться по идентификатору, за исключением фактов, которые необходимо фильтровать и сортировать для корректного построения состояния контракта.
4.4.1 Компонента обработки событий
Компонента contract-processor имеет несколько точек входа:
Жизненный цикл контракта - ContractLifecycleEventConsumer;
Внешние события контракта - ExternalContractEventConsumer;
Действия пользователей - ContractActionEventConsumer;
Ответы сервиса баланс - BalancePaymentResultEventConsumer.
Обработка событий жизненного цикла контракта
Каждое событие жизненного цикла контракта обрабатывается согласно структуре, изображённой на рисунке ниже.
В зависимости от схемы контракта и типа события вызывается соответствующий обработчик из реестра. Всего существует три типа событий жизненного цикла контракта, а именно:
Создание контракта мастером (см. DefaultContractInitContractLifecycleProcessor);
Инициализация контракта на дочернем узле (см. DefaultNodeInitContractLifecycleProcessor);
Создан факт контракта (см. DefaultContractFactContractLifecycleProcessor).
Так как факты контракта различаются по типу и обработка подразумевает, что все они будут обрабатываться согласно их назначению, то в пакете com.lastrix.mps.contract.service.schema._default.fact можно найти реализацию обработки каждого типа факта контракта.
Полный список:
ErrorContractFactDetails - не обрабатывается (не реализовано);
PaymentHoldContractFactDetails - если все переводы, согласно текущему состоянию контракта, выполнены, то переводит обработку контракта в состояние завершения, иначе для всех операций списания формирует пополнения на целевые кошельки согласно платежам (см. PaymentHoldDefaultContractFactHandler);
StatusContractFactDetails - глобальные изменения статуса контракта, отправляемые мастером и дочерними узлами, при получении данного статуса узел понимает может ли он изменить статус или нужно ждать, см пакет com.lastrix.mps.contract. service.schema._default.fact.status;
PaymentAddContractFactDetails - не обрабатывается, информационный факт;
WalletAddContractFactDetails - не обрабатывается, информационный факт;
WalletAddInfoContractFactDetails - не обрабатывается, информационный факт.
Так же присутствуют локальные факты контракта, не покидающие текущий узел:
AddWalletDepositRequestContractFactDetails - фиксация информации для отправки зачисления ресурсов асинхронно в соответствующий сервис (см. AddWalletDepositRequestDefaultContractFactHandler);
AddWalletWithdrawRequestContractFactDetails - фиксация информации для отправки списания ресурсов асинхронно в соответствующий сервис (см. AddWalletWithdrawRequestDefaultContractFactHandler);
ApprovalContractFactDetails - обработка разрешения пользователя на проведение транзакций (см. ApprovalDefaultContractFactHandler);
LocalStatusContractFactDetails - локальные изменения статуса контракта, генерируемые узлом (см. LocalStatusDefaultContractFactHandler);
NodeContractFactEventIdContractFactDetails - не обрабатывается, информационный факт для кеширования информации;
NodeEventCategoryEventIdContractFactDetails - не обрабатывается, информационный факт для кеширования информации.
Следует уточнить разницу между статусом контракта и локальным статусом контракта. Это разные сущности. Каждый узел, когда видит, что можно изменить статус создаёт событие и рассылает всем остальным узлам. Только когда мастер создаёт такой же статус, а делает он это обычно после всех дочерних узлов, все узлы имеют права изменить локальный статус и выполнить ассоциированные со статусом операции. Без подобной синхронизации обработка станет невозможной, а главное это не позволит своевременно обнаруживать ошибки исполнения контракта.
Примеры обработки контрактов приведены в Приложениях 1 и 2.
4.4.2 Компонента RestAPI
Компонента отвечает за создание контрактов пользователем и получение состояния контракта сервисами узла. Пользователь не может напрямую получить информацию о контракте через этот сервис.
4.5 Сервис истории контрактов
Данный сервис состоит из компонент: mireapay/node/contract-history/processor/ и mireapay/node/contract-history/api/ .
В отличие от сервиса контрактов - история не нуждается в высокоскоростном доступе к данным контрактов, но вот возможность просматривать списки с сортировками и фильтрами - является необходимостью. Поэтому ядро работает с одной моделью, а история - со своей. Это не только позволяет разгрузить ядро от тяжёлых операций, которые снизят производительность, но так же оптимизировать хранимый объем информации. Очевидно, что после завершения контракта вся служебная информация не может быть нужна.
Базу данных сервиса можно найти в проекте mireapay/node/contract-history/database-liquibase/. В таблицу contract_history_tab заносится информация по контракту. Процесс регистрации контракта происходит следующим образом:
При создании контракта создаётся запись с полем document равно нулю;
При отмене контракта или его завершении происходит обновление поля finishedAt и установка поля document.
Таким образом в таблице никогда не удаляются записи, только добавляются новые или обновляются поля. Таблица партицируется по дате создания контракта.
4.5.1 Компонента обработки событий
Компонента contract-history-processor обрабатывает события жизненного цикла контрактов. Входной точкой обработки является класс ContractHistoryEventConsumer, далее событие в зависимости от типа маршрутизируется в один из обработчиков.
Существует три обработчика событий жизненного цикла контракта:
.processor.events.history.processors.CreatedContractHistoryProcessor - создаёт запись и регистрирует доступы локальных кошельков в контракту;
.processor.events.history.processors.CancelledContractHistoryProcessor - обновляет запись в БД;
.processor.events.history.processors.CompleteContractHistoryProcessor - обновляет запись в БД.
4.5.2 Компонента RestAPI
Сервис позволяет получать текущее состояние контракта по уникальному ключу, реализация находится в классе ContractHistoryRestAPIV1Controller. Если контракт ещё активен, то состояние будет подтягиваться из contract-api, а не БД истории контрактов.
4.6 Сервис кошельков
Сервис состоит только из одной компоненты - mireapay/node/wallet/api/.
База данных mireapay/node/wallet/database-liquibase/. Каждый кошелёк, зарегистрированный на узле должен иметь запись в таблице wallet_tab и идентифицирующие документы в identity_document_tab.
Пример генерации кошельков:
INSERT INTO wallet_tab(description)
SELECT ('{"type": "WALLET_DESC_FIO", "lastName": "Иванов'
|| wallet_id || '", "firstName": "Иван' || wallet_id ||
'", "middleName": "Иванович' || wallet_id || '"}')::jsonb
FROM generate_series(100000, 200000) as wallet_id;
Удостоверяющего документа (ИНН):
INSERT INTO identity_document_tab(wallet_id, content)
SELECT wallet_id,
('{"type": "IDENTITY_DOCUMENT_INN", "number": "500123978456'
|| wallet_id || '"}')::jsonb
FROM generate_series(100000, 200000) as wallet_id;
По идентификатору кошелька сервис контрактов получает информацию о кошельке и добавляет ее в контракт при создании.
4.7 Адаптер клиринга
У каждого узла может быть адаптер, отправляющий все регистрируемые контракты в завершённом состоянии в клиринг, исходный код можно найти в проекте mireapay/node/adapter/clearing/.
4.8 Сервис налоговых шаблонов
Сервис состоит только из одной компоненты - mireapay/node/tax-matcher/api/.
База данных mireapay/node/tax-matcher/database-liquibase/.
Налоговые шаблоны заводятся в ручную. Например, что бы списывать с плательщика процент за перевод другому лицу, нужно выполнить несколько шагов.
Шаг первый. Создаём в таблице wallet_range_tab запись для указания локальных кошельков, на которые будут зачисляться налоги:
INSERT INTO wallet\_range\_tab (external\_id, description, min\_id, max\_id)
VALUES ('DEFAULT', 'DEFAULT tax wallets', 3, 100);
Теперь при генерации налога будет выбираться случайный кошелёк узла из начиная с 3 до 100.
Шаг второй. Добавляем шаблон налога:
INSERT INTO tax_matcher_tab (external_id, description,
wallet_range_id, payload)
VALUES ('DEPOSIT_FEE', '1% deposit fee', 1, '<JSON>');
Где '<JSON>' - документ, описывающий шаблон, например:
{
"type": "TAX_MATCHER_DEFAULT",
"matcher": {
"type": "ALL_OF",
"children": [
{
"type": "DEFAULT_CURRENCY",
"minAmount": 0,
"currencyCode": "RUB"
},
{
"type": "DEFAULT_SAME_NODE"
},
{
"type": "DEFAULT_TRANSFER_TYPE",
"transferType": "DEPOSIT"
}
]
},
"evaluator": {
"type": "CURRENCY_TAX_EVAL",
"percent": 0.1
},
"accumulatorSource": {
"type": "NO_ACCUM"
}
}
Согласно данному шаблону с перевода средств будет взят 10% налог за перечисление средств, при выполнении всех условий:
Кошелёк зарегистрирован на текущем узле;
Сумма платежа не менее 0 и валютой является рубль;
По отношению к контракту кошелёк является плательщиком - переводит средства другой стороне.
4.9 Шлюз сообщений
Сервис состоит из одной компоненты mireapay/node/intercom/http-gate/. Данное приложение слушает события жизненного цикла контрактов предназначенные для других узлов. Каждое сообщение целевое, отправляется конкретному узлу. От сервиса ожидается, что сообщение будет гарантированно доставлено.
Для отправки сообщений используется http-клиент.
Заключение
В данной работе был предложен эскиз распределенной платёжной системы, позволяющей рассматривать субъекты платёжной системы (государства, но в т.ч. банки), как независимые друг от друга стороны. Взаимоотношения возможны в этой системе только при обоюдных договорённостях, ни одна из сторон не может как-то повлиять на другую, навязать другой свою волю на основе прямого контроля потока информаций. Сторону невозможно отключить от системы, если только вообще все стороны откажутся сотрудничать, что в наше время является утопией.
Предложенный эскиз не претендует на единственно истинный способ построения платёжных систем, т.к. в зависимости от требований они могут приобретать самые непредсказуемые очертания. Основной целью данной работы было создание бесконечно масштабируемой системы, которая позволит гарантировать невозможность перезаписи истории, дать инструменты контроля и наблюдения другой стороне, при этом не позволяя ей получать весь объем проводимых операций.
Предложенная система может самым гибким образом быть реструктурирована и подогнана под нужды конкретного государства. В то же время оставляя возможность общения между государствами на основе общей схемы и инфраструктуры, поддерживаемой всеми сторонами экономических взаимодействий. Построенная однажды инфрастуктура не потребует изменений, только поддержания в рабочем состоянии или обновления с целью достижения более лучших показателей передачи данных.
Данная работа является открытой, автор передаёт созданную интеллектуальную собственность в общественное пользование. Если вы желаете сделать автору приятно - сделайте пожертвование РТУ МИРЭА или нашим бойцам ВС РФ.
Приложение 1
Разбор обработки контракта на одном узле. Предположим, что имеется два кошелька 1 и 2, отсутствуют налоги или комиссии, требуется перевести 1 рубль с первого кошелька на второй. На первом кошельке имеется достаточно средств для списания.
Тогда работу узла можно разделить на следующие шаги.
Шаг 1. Создание контракта пользователем
Пользователь выполняет запрос по созданию и запуску контракта через RestAPI. При создании контракта будут созданы следующие факты:
Регистрация кошелька 1;
Регистрация кошелька 2;
Регистрация информации о кошельке 1;
Регистрация информации о кошельке 2;
Добавлена информация о переводе 1 рубля с кошелька 1 кошельку 2;
Статус инициализации.
После создания компонента RestApi контрактов отправляет событие жизненного цикла контракта с типом "создан контракт".
Шаг 2. Обработка события жизненного цикла контракта создания контракта
Т.к. в обработке участвует только один узел, то обработчик событий создаёт событие создания факта с изменением статуса на активно.
Шаг 3. Обработка события изменения статуса активно
Ввиду того, что узел один - компонента создаёт событие изменения локального статуса на активно.
Шаг 4. Обработка события изменения локального статуса активно
Компонента обработки событий контрактов получит все кошельки для списания средств (в данном случае кошелёк 1), и создаст для каждого списания факт с информацией, необходимой для генерации события в сервис соответствующего ресурса (т.е. в сервис баланса для списания 1 рубля).
Шаг 5. Обработка события генерации списания
Компонента обработки событий контрактов генерирует событие для сервиса баланса и отправляет его. Когда сервис баланса обработает запрос, то вернёт результат успешно. Обработка результата подразумевает создание факта блокировки средств кошелька.
Шаг 6. Обработка факта блокировки средств
Компонента проверяет, кому можно зачислить заблокированные средства. Для каждого кошелька (т.е. кошелька 2), создаётся событие генерации события пополнения средств.
Шаг 7. Обработка события генерации пополнения
Компонента отправляет событие в сервис баланса. Результатом будет успешное заморозка пополнения. На успешный результат будет создан факт заморозки средств кошелька.
Шаг 8. Обработка факта заморозки средств
Компонента обнаружит, что все переводы выполнены. Создаётся факт изменения статуса завершения.
Шаг 9. Обработка факта изменения статуса завершения
Контракт обрабатывается одним узлом, поэтому сразу создаётся факт локального изменения статуса завершения.
Шаг 10. Обработка факта изменения локального статуса завершения
Компонента отправляет в сервисы, на которых блокировались ресурсы подтверждение транзакции. В нашем случае будет отправлено 2 события сервису баланса - списание с кошелька 1 и пополнение кошелька 2. Ввиду того, что контракт обрабатывается только одним узлом, то сразу создаётся событие изменения статуса завершено.
Шаг 11. Обработка факта изменения статуса завершено
Контракт обрабатывается одним узлом, поэтому сразу создаётся факт локального изменения статуса завершено.
Шаг 12. Обработка факта изменения локального статуса завершено
Контракт маркируется успешно завершённым, отправляются нотификации пользователям кошельков 1 и 2.
Для работодателей
Работодатель, поспеши завести себе Java-кота в команду! Мышей не ловит, но зато пишет Java-код, а еще проектирует немножко.
https://hh.ru/resume/b33504daff020c31070039ed1f77794a774336