5.8 KiB
Bot Microservices: Hasura + Fastify
Ten dokument jest kontraktem architektonicznym dla nowych serwisow bota.
Zasady
- Zawsze najpierw powstaje dokument kontraktu serwisu, potem implementacja.
- Hasura jest jedynym source of truth dla danych i stanu wspoldzielonego.
- Fastify jest shell-em mikroserwisu: HTTP, health, readiness, trigger, observability.
- Logika domenowa nie siedzi w Fastify handlerach. Jest w czystych
modulach
processing. - Serwisy maja byc male i wyspecjalizowane. Kazdy liczy jeden skladnik decyzji albo jedna spojna klase skladnikow.
- Serwis nie staje sie drugim systemem stanu. Jesli wynik ma byc wspoldzielony, zapisujemy go do Hasury albo wystawiamy przez endpoint.
- 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 /healthzGET /readyzGET /result/latestGET /result/:idPOST /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:
bot-config-service- czyta konfiguracje z Hasury
- wystawia znormalizowany kontrakt configu
market-snapshot-service- czyta candles i derived DLOB z Hasury
- wystawia ujednolicony snapshot rynku
momentum-service- liczy
mom_3s,mom_10s,mom_30s,vol_30s
- liczy
microstructure-service- liczy spread, depth, imbalance, slippage
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.texopisuje 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 + volatilityto dobry jeden serwis, bo oba licza sie z candlesspread + depth + imbalance + slippageto dobry jeden serwis, bo wszystkie licza sie z DLOBgating + scoring + final observer resultlepiej trzymac razem na poczatku niz rozbijac zbyt drobnomom_3s,mom_10s,mom_30sjako trzy osobne procesy nie maja sensu
Rekomendowane granice dla strategii v1
Najbardziej sensowny podzial runtime dla obecnej strategii:
market-snapshot-service- jeden odczyt z Hasury
- candles i derived DLOB
momentum-service- wszystko liczone tylko z candles
mom_3s,mom_10s,mom_30s,vol_30s
microstructure-service- wszystko liczone tylko z derived DLOB
- spread, depth, imbalance, slippage
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.