Архитектура проекта tbot

На этой схеме представлены сервисы проекта tbot и их взаимодействие. Направление стрелок показывает направление «движения» данных. Здесь и далее термин сервис и микросервис идентичны.

Вообще я считаю, что одна из серьезных проблем, связанных со сложностью проектирования микросервисных систем вызвана именно этой приставкой «микро». Она просто сбивает с толку. Что значит микро? Это что, намёк на то, что сервис должен быть маленьким?! А почему маленьким? А насколько маленьким?

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

Лично мне запомнилось такое высказывание:

Тогда я не знал о существовании концепции микросервисов — как, впрочем, отрицаю их и сейчас. Никаких микросервисов нет — есть только сервисы. Например, водяной насос в моей машине представляет критический сервис, а его длина составляет всего 20 сантиметров. Водяной насос, который местная водопроводная компания использует для подачи воды в мой город, выполняет очень полезную функцию для города, но его длина превышает 2,5 метра. Оттого, что где-то существует насос больших размеров, насос в моей машине не становится микронасосом, он все равно остаётся насосом. Сервисы остаются сервисами независимо от размера.

Джувел Лёве, «Совершенный софт».

Тема микросервисов многогранна, там много всякого интересного, и я думаю, что ещё посвящу ей одну или несколько статей. А пока вернёмся к представленной выше схеме.

Любое решение, основанное на микросервисах несёт в себе мотивацию разделения. Давайте поделим какую-то потенциально* большую кодовую базу на несколько более-менее изолированных частей, которые мы назовём сервисами / микросервисами и некоторым образом организуем их взаимодействие.

*Потенциально в том смысле, что мы пока ещё ничего не написали, но уже понимаем, что писать много. А может быть мы уже имеем большую кодовую базу, например, какой-нибудь унаследованный монолит, который мы хотим растащить на сервисы.

За таким желанием обычно есть веские аргументы, по которым это действительно стоит делать, т.е. делить систему на сервисы. По крайне мере мне хочется в это верить, увы, но мода бывает не только на какие-нибудь дырявые джинсы, но и на микросервисы она тоже есть)

Однако не суть. В центре нашего внимания сейчас не вопрос зачем, а вопрос как? Как делить систему на сервисы? Это наверное самый главный вопрос для архитектора, техлида и др. товарищей, принимающих проектные решения Опять же есть разные варианты ответа на этот вопрос, разные не только в контексте конкретной системы, но и в методологическом смысле тоже, т.е. к этому можно вообще по-разному подходить. Кто-то ратует за DDD, кто-то за функциональное разделение, кто-то за разделение на основе изменчивости, вариантов хватает. Понятно, что они не идеальны, серебряной пули нет и ошибок в проектировании хватает. И независимо от того насколько умен и опытен проектировщик, он не может предвосхитить весь проект, учесть все будущие требования, которые бизнес заказчик обязательно добавит в процессе разработки.

Лично для себя я сформулировал один постулат, которым мне помогает принять этот факт.

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

Просто примите это как факт. Вы делаете черновой вариант системы и на данном этапе сгодится практически любое более-менее адекватное проектное решение. Все равно его потом придётся переделывать)

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

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

По поводу нейминга. Здесь я пробовал некоторые архитектурные идеи, которые изложил Джувел Лёве в своей книге «Совершенный софт» (я цитировал его ранее). Не скажу, что вот так прямо бери и используй то, что предлагает автор, но дельных мыслей там много. Что-то я смог применить, от чего-то отказался, что-то модифицировал под себя. В моем случае система относительно простая и плодить массу сервисов нет никакого смысла. Всего их четыре:

  • ipm — Incoming Price Manager
  • anam — Analytics Manager
  • demm — Decision Making Manager
  • pr — Price Repository

Сервисов, идущих в условный прод здесь только три: ipm, anam, demm.

Сервис pr обеспечивает сохранения данных, полученных от Тинькофф инвестиций. Нужно это для того, чтобы сначала накопить исторические данные, а затем моделировать работу бота на этих данных. Это в свою очередь нужно для того, чтобы обеспечить детерминированную воспроизводимость алгоритмов, связанных с трейдингом, что позволяет провести сравнение разных вариантов алгоритма на одних и тех же данных. В целом Price Repository носит вспомогательный характер, для прода он не нужен. Однако он важен на этапе разработки, без него практически невозможно отлаживать и проверять торговые алгоритмы.

ipm выступает поставщиком данных для всех сервисов. Для pr он транслирует данные на сохранение, для anam он предоставляет результаты сделок — цена, объем, время сделки, тикер ценной бумаги, а для demm текущие состояние книги заказов — стакан* в моменте.

*Я не буду сейчас углубляться в тему трейдинга, желающие легко могут загуглить словосочетание «биржевой стакан», вот одна из поясняющих статей: «О чем могут рассказать биржевой стакан и лента сделок».

Почему в anam сделки (trade), а в demm стакан (order book)? Потому что аналитика у меня пока только для сделок, т.е. anam рассчитывает какие-то торговые индикаторы* по сделкам, а стакан по-любому нужен в demm. Состояние стакана позволяет понять текущую цену покупки и продажи, очевидно что это необходимо в сервисе, который принимает решение о том выставлять заявки на покупку или продажу.

*Это опять трейдинговая тема, если очень коротко, то индикаторы — это статистические показатели по сделкам за некоторый интервал. Опять же это легко гуглится по словам «индикаторы технического анализа».

По мимо стакана demm получает подготовленную аналитику по сделкам от anam, т.е. ему не нужны чистые сделки, здесь в отличие от стакана ему нужны уже обработанные, посчитанные данные, т.е. статистические показатели. Он строит на их основе прогнозную модель и пытается предсказать поведение цены, например, какой будет тренд в ближайшие несколько минут? Цена будет расти, падать или останется неизменной?

Если прогнозная модель работает хорошо, то появляется возможность предсказывать поведение цены и пользуясь этим в итоге зарабатывать. Ключевое слов здесь «если». На практике это выливается в многочисленные эксперименты: мы можем менять индикаторы (расчёты на стороне anam), можем менять способ построения прогнозной модели (на стороне demm). И вот как раз для этого нужно иметь возможность воспроизводить детерминированную последовательность данных — то, что может делать pr.

Те стрелки на схеме, которые обозначены пунктиром как раз и подразумевают, что это такой режим работы, когда pr транслирует ранее сохранённые данные. Если вы обратите внимание на один из режимов запуска pr — режим чтения данных, то увидите что в качестве параметров выступает дата, за которую данные были сохранены и скорость, с которой данные будут воспроизводиться. Очень важно уметь быстро «прогонять» весь набор данных.

Представьте, что прогнозная модель «учится» в течение 1 часа. Тогда каждый эксперимент с новой версией алгоритма будет занимать ровно час. Это довольно накладно, если учесть что при разработке таких систем требует провести тысячи разных экспериментов. Именно для этого введена возможность ускорять прогон данных.

Здесь еще есть неочевидные моменты, затрагивающие взаимосвязи сервисов. На первый взгляд pr связан с anam и demm, но на самом деле это не так. Price Repository не знает ни об одном из сервисов в этой системе. Он реализован как сервер, к которому подключаются др. сервисы. Так, например, ipm подключается, чтобы отправть данные на pr, а anam и demm подключаются, чтобы подписаться на gRPC Stream, который pr организует в режиме чтения данные (воспроизведение исторических данных).

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

Валерий Чугреев, 21.05.2021

Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии