
Анализ, архитектура, вайбкодинг и даже комиксы — всё это здесь.
Цели статьи: 1) проверить жизнеспособность концепции максимально простого и дешевого интернет-банкинга и заодно 2) протестировать возможности ИИ в качестве инструмента прототипирования.
Спойлер: пожертвовал брокерами сообщений и Redis. В итоге, на бэке только сервисы (Go) с реляционными БД (PostgreSQL).
Вот, что получилось - https://justabank.ru
Что касается ИИ: использовал поочередно Claude Sonnet 4.5, deepseek reasoner, gpt-5.4.
Теперь к делу…
Наверное, не надо объяснять почему хочется просто и дёшево. Но надо проверить, будет ли “просто” вести к “дешево” и получится ли такой интернет-банкинг (ИБ) хорошим.
Хороший, в моем понимании, — это когда ИБ функциональный (делает то, что нужно клиенту), дешевый, надежный, масштабируемый и быстро расширяемый по функциональности.
Начнем…
Определим минимальные функциональные требования для нашего пилотного ИБ. Что он должен уметь:
отображать счета,
создавать счета до востребования,
переводить деньги между счетами,
выдавать кредиты (сделать примитивную выдачу просто, чтобы на счетах появились деньги).
Нет цели во всех подробностях воссоздать банковский бэкенд. Цель – проверить концепцию возможности создания очень простого и дешевого ИБ.
Желания.
То, что в обычных сложных и душных банках называют распоряжениями, поручениями и заказами (orders), я в своем простом проекте ИБ буду называть “желаниями”. Кроме того, что это понятие простое (а у меня все просто), такое название лучше всего отражает суть данной сущности. Ее смысл зафиксировать, что клиент хочет от банка, т.е. это буквально желание клиента.
Можно даже сравнить банковское приложение со сказочным джинном: вместо лампы трем телефон, появляется джин приложение, говорим желание (перевести деньги, взять кредит) и оно исполняется. Да, спектр желаний не тот, что у джинна, но зато исполнение желаний под контролем регуляторов.
Вообще, я считаю, что функционал предоставления клиенту возможности рассказать банку о его намерениях (желаниях) касательно банковских продуктов/услуг — это главная функция системы интернет-банкинга.
И эта функция простая, ведь технически, задача ИБ сводится к тому, чтобы подготовить и передать в банковский бэкенд json вида:
{ “wish”: { “some_parameter”: “some value”, “another_parameter”: “some value”, … } }
Хочу подчеркнуть, цель существования ИБ – обеспечить поступление в банк “wish-json’ов”, которые понятны сервисам-исполнителям желаний, и которые банк может исполнить с юридической и технической точек зрения.
Т. е. первое на чем я предлагаю сфокусироваться — это формирование желаний. Что интересно, это действительно очень просто. Процесс выглядит так:

Кроме удобства формирования желаний, важно сделать удобное и понятное подтверждение желаний. Чтобы не получилось, как в анекдоте про золотую рыбку (“... мужики, давайте еще раз ее поймаем, а то я какую-то фигню загадал...”). Клиенту нужна уверенность, что банк исполнит желание именно так, как оно описано, а банку нужно убедиться в том, что клиент ознакомился со всеми параметрами желания и они его устраивают.
На этом формирование желания закончено. Дальше исполнение, которое относится больше к “банк”, чем к “интернет”. Важно понимать, где заканчивается желание и начинается исполнение. Даже если в банке “Гринготтс” нет никакой банковской бэкенд системы, а счета и вклады ведут гоблины, делая записи в больших бухгалтерских книгах, все равно там можно сделать ИБ: веб/мобильное приложение + сервис, который будет высылать сформированный “wish”: {...} на email гоблина.
Далее, периодически, я буду приводить правила (информация, которую ИИ держит в голове при обработке промптов) в сокращенном виде, но я проверял, что их достаточно, чтобы ИИ правильно понимал промпты и адекватно запрашивал уточнения.
В файле правил ИИ опишем концепцию желаний:
Скрытый текст
## Концепция желаний (Wishes)
Правило: Банковская операция выполняется только через желание
- Клиент никогда не вызывает операцию напрямую — он создаёт желание (Wish)
- Желание проходит полный жизненный цикл: черновик → подтверждение → исполнение
Правило: Жизненный цикл желания
- draft — клиент заполняет форму; поля сохраняются инкрементально через SetField
- pending — форма отправлена: PDF сгенерирован, код подтверждения отправлен на email (TTL 10 мин)
- executing — клиент успешно подтвердил операцию, после чего запущено исполнение
- completed — polling-job обнаружил успех исполнения
- cancelled — polling-job обнаружил отмену исполнения (компенсация прошла)
- failed — polling-job обнаружил ошибку исполнения (dirty state) или истёк TTL pending
Универсально описанные желания.
Универсальность желаний особенно важная вещь. Желания на исполнение могут принимать разные сервисы – если каждый будет делать это по-своему, то будет дорого и сложно разрабатывать эти сервисы, поддерживать их и писать к ним UI. Поэтому нужно это дело стандартизировать.
Скрытый текст
Правило: Универсальная форма желания
- Все операции описываются через единый формат
- Каждое поле содержит метаданные для отображения (тип, название, …)
- Форма возвращается фронтенду как есть — включая опции select, текущие значения и ошибки валидации
Подтверждение желаний.
Тема важная, но в данной статье я не хочу смещать фокус читателя в сторону юридических и технических тонкостей процесса подтверждения операций. Ограничусь заметкой, что в данном приложении я применил свой, как мне кажется простой и дешевый вариант подтверждения операций, т.е. это не пример как надо делать, а просто видение реализации, которое отличается от наиболее распространенных банковских решений. Короче говоря, это кастомная реализация подтверждения операции кодом.
Исполнение желаний.
Исполнение желания должно быть реализовано как единый бизнес-процесс с явным состоянием, компенсациями и финальным согласованным результатом. Важно понимать, что, приняв желание, банк берет на себя обязательство либо исполнить его полностью, либо не исполнить вообще. Частичное исполнение не должно считаться успешным; при сбое система обязана компенсировать выполненные шаги или перевести исполнение в состояние ручного разбора.
Важный аспект: регистрация желаний — это интерактивный процесс взаимодействия с клиентом, а исполнение — это чистый бэкенд под капотом. Эти процессы нужно разделять. У них разная ответственность, сложность, исполнители и характер нагрузки. Например, желания сложнее масштабировать, чем исполнения; исполнения можно поставить в очередь, а желания у тысяч клиентов могут возникнуть одновременно. В “зарплатный день” сильно больше желаний сделать перевод или открыть вклад, чем за день до этого.
Скрытый текст
Правило: Сага-оркестратор
- Инфраструктурный движок саги вынесен в отдельную библиотеку
- Движок не знает про Wish — он работает только с Execution и SagaStep
- Доменные шаги реализуются в каждом сервисе-исполнителе через интерфейс StepHandler:Execute и Compensate
- Новый сервис регистрирует обработчики через orchestrator.New(...).WithStepHandler(...)
- Шаги саги персистируются в таблице processing.saga_steps до начала исполнения
- Каждый шаг имеет статус: pending → executing → completed / failed
- При ошибке шага — компенсация выполненных шагов в обратном порядке через Compensate
- Опрос статуса внешних операций (polling) — интервал 500ms, таймаут контекста 2 минуты
- Оркестратор обновляет только execution.status; wish.status не трогает
Правило: Синхронизация статуса желания (polling-job)
- Сервис-исполнитель содержит фоновый job runStatusSyncJob с интервалом 2 секунды
- Job опрашивает все желания в статусе executing с непустым execution_id
- Для каждой пары (wish_id, execution_id) запрашивает execution.status
- Если статус финальный (completed/cancelled/failed) — обновляет wish.status соответственно
Правило: Документы желания
- PDF генерируется в момент перехода draft → pending (до отправки кода)
Правило: Execution.Payload
- Execution не хранит wish_id — связь только в обратную сторону (wishes.execution_id)
- Оркестратор и хендлеры шагов берут все параметры операции из execution.payload
Ближе к делу. Набросаем архитектуру.
Банк – не банк, если у него нет бухгалтерской системы. Такая система — это сервис (или группа сервисов), в котором живут счета и проводки. В нашем прототипе это будет сервис accounts.
Это сердце банка. Он ни в коем случае не должен быть доступен снаружи. В сервисе живут не только счета, которые мы хотим показывать на дашборде ИБ, но и технические счета. Это вообще может быть коробочная система, которую нельзя дорабатывать. Accounts не обязан знать про желания клиента, но должен предоставлять внутренний api для действий с сущностями, за которые отвечает (счета, проводки).
Дашборд клиентского приложения (пусть это будет какое-нибудь SPA) нуждается в сервисе, который предоставит api получения счетов. Назовем этот сервис “funds”. В нем же я реализую api формирования желаний переводов, ведь этот сервис отвечает за манипулирование деньгами клиентов. Здесь, я так же реализую формирование желаний создания счетов и кредитов.
Почему бы и нет? Нет, потому что funds не владелец счетов и кредитов. В настоящем банке будут отдельные домены счетов, вкладов и кредитов, но в этом проекте я упрощу доменную модель, поскольку сейчас мне важно проверить реализацию создания и исполнения желаний на примере переводов между счетами. А создание счетов и кредитов в моей реализации — это вспомогательные элементы. Каким образом funds будет получать данные по счетам клиента – это отдельная песня, которую обсудим позже.
Сервис авторизации. Куда же без него. Сделаю двухфакторную авторизацию через почту.
Отдельный микросервис уведомлений делать не буду, ограничусь общей библиотекой отправки email. У меня же всё максимально просто.
Итак, архитектура такая:

Само собой сервисы stateless и работают в multi instance кластере. Cостояние находится в БД, а фоновые процессы должны быть идемпотентны или защищены offset/locking. Поднимать буду просто в docker контейнерах. Пока обойдемся без кубера.
Поскольку проект простой выберу простой язык - Go. Он действительно простой, популярный, много библиотек, много успешных проектов. Я никогда на нем не писал, но от этого только интереснее.
Почему не python? Ведь он считается простым. Дело вот в чем:
Скрытый текст
Python мне просто не нравится.
СУБД - Postgres. Простая классика, зарекомендовавшая себя в финтехе.
Транспорт.
Внешний api особо без вариантов - http. Внутренний межсервисный синхронный транспорт - gRPC + protobuf, поскольку преимущества над http очевидны (не буду копировать сюда результаты гугления “rest vs grpc”), а усложнений практически не несёт и все знают эту технологию.
Брокеры сообщений.
Kafka, по моим личным наблюдениям, в качестве mission critical брокера сообщений это вещь очень дорогая во всех смыслах: и по железу, и по обслуживанию. В интернете есть расчеты и объяснения почему сочетание “Kafka уровня критичности MC + большая пропускная способность” это дорого. Это дорого даже для Kafka в качестве брокера.
RabbitMq, опять же по моим наблюдениям, сильно дешевле, но тем не менее это отдельный общий компонент архитектуры, который её усложняет.
Думаю, стоит проверить, можно ли построить систему без брокеров, и не приведет ли такая попытка упрощения и удешевления к обратному эффекту – к дорогому усложнению.
Да кому эти брокеры сообщений вообще нужны?
Сначала комикс (рисовал GPT), а потом разберем этот вопрос вместе с ИИ…

Спросим у ИИ (ChatGPT): “Скажи мне ИИшечка, зачем люди используют брокеры сообщений?”
Спойлер, краткий итог моей переписки с ИИ (моя интерпретация): Для сообщений типа “команда” (речь о конкретном кейсе реализации банковских операций) – лучше RPC. Для сообщений типа: “событие-факт” – брокер.
Т.е. в контексте разработки банковского бэкенда мы с ИИ пришли к выводу (возможно, сказалось мое психологическое давление на ИИ), что исполнение операции лучше сделать по схеме: saga + rpc polling сервисов исполнителей. А, к примеру, отгрузку событий изменения баланса клиентских счетов для обновления дашборда лучше сделать с использованием брокера. Но я сделаю все без брокера, просто потому что интересно насколько сложным получится итоговое решение репликации событий.
Подробная переписка с ИИ. Она длинная, поэтому можете “перемотать” к выводу, но мне кажется, что это интересный диалог и его стоит посмотреть.
Скрытый текст
ИИ:
