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

210 lines
5.8 KiB
Markdown

# 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.