Files
trade-doc/bot-microservices-hasura-fastify.md

5.8 KiB

Bot Microservices: Hasura + Fastify

Ten dokument jest kontraktem architektonicznym dla nowych serwisow bota.

Zasady

  1. Zawsze najpierw powstaje dokument kontraktu serwisu, potem implementacja.
  2. Hasura jest jedynym source of truth dla danych i stanu wspoldzielonego.
  3. Fastify jest shell-em mikroserwisu: HTTP, health, readiness, trigger, observability.
  4. Logika domenowa nie siedzi w Fastify handlerach. Jest w czystych modulach processing.
  5. Serwisy maja byc male i wyspecjalizowane. Kazdy liczy jeden skladnik decyzji albo jedna spojna klase skladnikow.
  6. Serwis nie staje sie drugim systemem stanu. Jesli wynik ma byc wspoldzielony, zapisujemy go do Hasury albo wystawiamy przez endpoint.
  7. Podzial na mikroserwisy robimy po granicach domenowych i kontraktach danych, nie po pomocniczych funkcjach.

Model runtime

Kazdy serwis ma ten sam ogolny przeplyw:

Hasura GraphQL -> input adapter -> processing -> output adapter -> Fastify endpoint

Warstwy:

  • inputs/hasura
    • zapytania GraphQL
    • mapowanie rekordow Hasury do lokalnych struktur
  • processing
    • czyste funkcje liczace cechy, score, klasyfikacje, gating
  • outputs/hasura
    • zapis wyniku, eventu, state snapshotu
  • transport/http
    • Fastify
    • endpointy health, ready, latest result, admin trigger
  • orchestration
    • petla, harmonogram, retry, timeouty, kolejnosc krokow

Rola Fastify

Fastify nie jest miejscem na model matematyczny.

Fastify odpowiada za:

  • GET /healthz
  • GET /readyz
  • GET /result/latest
  • GET /result/:id
  • POST /run
  • start i stop serwisu
  • logowanie i metryki

Fastify moze odpalac processing synchronicznie albo przez worker/job, ale sama logika ma pozostac poza transportem.

Rola Hasury

Hasura jest source of truth dla:

  • konfiguracji bota
  • snapshotow i tabel pochodnych
  • wynikow i eventow serwisow
  • historii potrzebnej do debugowania i replay

Mikroserwis:

  • czyta z Hasury
  • liczy wynik
  • opcjonalnie zapisuje wynik do Hasury
  • opcjonalnie wystawia ten wynik przez HTTP

Nie trzymamy osobnego "prawdziwego" stanu tylko w RAM mikroserwisu.

Granica mikroserwisu

Dobry mikroserwis:

  • ma jeden glowny kontrakt wejsciowy
  • ma jeden glowny wynik
  • ma jasny owner danych
  • da sie niezaleznie uruchomic i przetestowac

Zly mikroserwis:

  • laczy wiele niespojnych modeli w jednym procesie
  • ma wiele roznych odpowiedzialnosci
  • staje sie nowym centrum stanu

Proponowany podzial

Minimalne serwisy obliczeniowe:

  1. bot-config-service

    • czyta konfiguracje z Hasury
    • wystawia znormalizowany kontrakt configu
  2. market-snapshot-service

    • czyta candles i derived DLOB z Hasury
    • wystawia ujednolicony snapshot rynku
  3. momentum-service

    • liczy mom_3s, mom_10s, mom_30s, vol_30s
  4. microstructure-service

    • liczy spread, depth, imbalance, slippage
  5. observer-service

    • scala wyniki serwisow skladowych
    • ocenia gate'y
    • liczy finalny wynik obserwacji
    • zapisuje event i aktualny state

Nie trzeba wdrazac ich wszystkich od razu jako osobne procesy. Najpierw dokumentujemy kontrakty, potem mozemy zaczac od modularnego monolitu i dopiero potem wycinac osobne procesy.

Czy bloczek modelu zawsze jest osobnym serwisem

Nie.

Bloczki z modelu matematycznego w bot1.tex sa optymalna jednostka dokumentacji i analizy. Nie kazdy bloczek jest jednak optymalna jednostka deploymentu.

Zasada:

  • bot1.tex opisuje bloczki matematyczne
  • kontrakt serwisu grupuje bloczki, ktore maja wspolne wejscie i wspolny wynik runtime
  • osobny proces wdrazamy dopiero wtedy, gdy taka granica ma sens operacyjny

To oznacza, ze:

  • momentum + volatility to dobry jeden serwis, bo oba licza sie z candles
  • spread + depth + imbalance + slippage to dobry jeden serwis, bo wszystkie licza sie z DLOB
  • gating + scoring + final observer result lepiej trzymac razem na poczatku niz rozbijac zbyt drobno
  • mom_3s, mom_10s, mom_30s jako trzy osobne procesy nie maja sensu

Rekomendowane granice dla strategii v1

Najbardziej sensowny podzial runtime dla obecnej strategii:

  1. market-snapshot-service

    • jeden odczyt z Hasury
    • candles i derived DLOB
  2. momentum-service

    • wszystko liczone tylko z candles
    • mom_3s, mom_10s, mom_30s, vol_30s
  3. microstructure-service

    • wszystko liczone tylko z derived DLOB
    • spread, depth, imbalance, slippage
  4. observer-service

    • pobiera wyniki serwisow skladowych albo liczy je lokalnie
    • ocenia gate'y
    • liczy score i finalny wynik
    • zapisuje eventy i state

To jest lepsze niz rozbijanie strategii 1:1 po kazdym wzorze, bo:

  • zmniejsza liczbe round-tripow do Hasury
  • zmniejsza narzut operacyjny
  • zachowuje czytelne granice domenowe
  • nadal pozwala skalowac glowne klasy obliczen niezaleznie

Zasada "small services"

Mniejsze serwisy sa preferowane, bo:

  • latwiej je skalowac niezaleznie
  • latwiej je testowac
  • latwiej je debugowac
  • latwiej wymienic jeden model bez ruszania reszty

Ale "mniejsze" nie znaczy "po jednej funkcji na proces".

Jednostka podzialu to:

  • spojny kontrakt danych
  • spojna odpowiedzialnosc obliczeniowa
  • sensowny deployment boundary

CPU i deployment

Mamy duzy zapas CPU, wiec wolno nam preferowac prostsze, bardziej izolowane procesy zamiast upychania wszystkiego do jednego serwisu.

To nie zmienia zasad:

  • nie duplikowac source of truth
  • nie mieszac transportu z logika
  • nie rozbijac na mikroserwisy bez kontraktu

Kontrakt dla nowego serwisu

Kazdy nowy serwis powinien miec dokument opisujacy:

  • nazwe i cel
  • wejscie z Hasury
  • wynik obliczen
  • zapis wyjscia do Hasury
  • endpointy Fastify
  • model bledow
  • SLA petli albo triggera

Stan biezacy

services/bot-observer zostal juz przesuniety w dobra strone:

  • Fastify jako shell
  • Hasura client osobno
  • snapshot loader osobno
  • model decyzji osobno
  • state/event write osobno

Kolejny krok to dalsze cięcie po kontraktach, nie po plikach.