JK
>ŁADOWANIE // ŁADOWANIE
INICJALIZACJA WIDOKU
Księga finansowa i backend nagród akcyjnych wdrażający księgowość podwójnego zapisu z dokładną wyceną w INR.
Zarządzanie nagrodami kapitałowymi wymaga bezwzględnej spójności transakcyjnej oraz odporności na awarie zewnętrznych źródeł cen.
| Technologia | Wersja | Cel | Dlaczego wybrano |
|---|---|---|---|
| Go | 1.25.1 | Język programowania | Solidna biblioteka standardowa, prymitywy współbieżności, szybka kompilacja |
| Gin | 1.11.0 | Framework HTTP, routing, middleware | Wysoka wydajność, wsparcie middleware, aktywna społeczność |
| PostgreSQL | 17 | Baza danych relacyjna | Zaawansowane funkcje SQL (widoki, LATERAL, kolumny generowane, ENUM) |
| golang-migrate | v4.19.0 | Migracje bazy danych | Prosty runner migracji oparty na plikach |
| logrus | 1.9.3 | Logowanie strukturalne | Ustrukturyzowane pola i logowanie z poziomami |
| go-sqlmock | v1.5.2 | Mockowanie bazy na potrzeby testów | Symuluje SQL w testach jednostkowych bez prawdziwej bazy |
| lib/pq | v1.10.9 | Sterownik PostgreSQL | Sprawdzony w boju, natywne wsparcie PostgreSQL |
| Docker | — | Konteneryzacja | Powtarzalne buildy, izolowane środowiska |
| Metoda | Trasa | Cel |
|---|---|---|
| GET | /health | Kontrola stanu — zwraca {status: OK} |
| POST | /api/v1/reward | Utwórz nagrodę — idempotentnie, transakcyjnie, z obliczaniem opłat |
| POST | /api/v1/adjustments/:id | Zastosuj korektę — blokowanie wiersza, wpisy księgowe według typu |
| GET | /api/v1/today-stocks/:userId | Dzisiejsze nagrody z widoku today_rewards |
| GET | /api/v1/historical-inr/:userId | Historyczna wycena w INR — paginowana, z mnożnikami zdarzeń |
| GET | /api/v1/stats/:userId | Podsumowanie statystyk — dzisiejsze nagrody + łączna wartość portfela |
| GET | /api/v1/portfolio/:userId | Szczegóły portfela — paginowane, filtrowane pod kątem wycofania z giełdy |
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| username | VARCHAR(50) | NOT NULL, UNIQUE |
| VARCHAR(100) | NOT NULL, UNIQUE | |
| created_at | TIMESTAMP | Domyślnie: NOW() |
Konta użytkowników. Zasilone jednym użytkownikiem testowym.
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| user_id | INT | FK → users(id) ON DELETE CASCADE |
| stock_symbol | VARCHAR(20) | NOT NULL |
| quantity | NUMERIC(18,6) | NOT NULL, wartość ujemna = odwrócenie |
| idempotency_key | VARCHAR(64) | UNIQUE, dopuszcza NULL |
| reward_date | DATE | GENERATED ALWAYS AS (created_at::DATE) STORED |
Główne zdarzenia nagród. Unikalność na (user_id, stock_symbol, reward_date) z wariantem ignorującym wielkość liter.
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| reward_id | INT | FK → rewards(id) ON DELETE CASCADE |
| entry_type | ledger_entry_type | ENUM: stock_units, inr_outflow, fees, brokerage_fee, stt_fee, gst_fee |
| stock_symbol | VARCHAR(20) | Dopuszcza NULL |
| quantity | NUMERIC(18,6) | Dopuszcza NULL |
| amount | NUMERIC(18,4) | Dopuszcza NULL |
Księga podwójnego zapisu. Każda nagroda generuje od 2 do 5 wpisów.
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| stock_symbol | VARCHAR(20) | UNIQUE |
| price | NUMERIC(18,4) | Bieżąca cena |
| updated_at | TIMESTAMP | Aktualizowane przez PriceService |
Ostatnia znana cena akcji. Aktualizowana przez PriceService w tle.
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| stock_symbol | VARCHAR(20) | NOT NULL |
| price | NUMERIC(18,4) | NOT NULL |
| date | DATE | NOT NULL, UNIQUE z stock_symbol |
Dzienne migawki cen do wyceny historycznej. Wstawiane/aktualizowane przez PriceService.
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| stock_symbol | VARCHAR(20) | NOT NULL |
| event_type | stock_event_type | ENUM: split, bonus, merge, delist |
| ratio_num | NUMERIC(18,6) | Licznik mnożnika |
| ratio_den | NUMERIC(18,6) | Mianownik mnożnika |
| new_symbol | VARCHAR(20) | Dla fuzji |
| effective_date | TIMESTAMP | NOT NULL |
Operacje korporacyjne. ratio_num/ratio_den definiuje mnożnik (np. split 2:1).
| Kolumna | Typ | Uwagi |
|---|---|---|
| idPK | SERIAL | Generowane automatycznie |
| reward_id | INT | FK → rewards(id) ON DELETE CASCADE |
| adjustment_type | VARCHAR(64) | reward_reversal, fee_refund, manual_correction |
| delta_quantity | NUMERIC(18,6) | NOT NULL |
| delta_amount | NUMERIC(18,4) | NOT NULL |
| reason | VARCHAR(255) | Dopuszcza NULL |
Modyfikacje istniejących nagród z blokowaniem na poziomie wiersza.
Jeden-do-wielu: users→rewards (CASCADE), rewards→ledger (CASCADE), rewards→adjustments (CASCADE). Niejawne odwołania krzyżowe: rewards.stock_symbol ↔ stock_prices.stock_symbol (przez UPPER()), rewards.stock_symbol ↔ stock_events.stock_symbol (mnożniki zdarzeń). Brak formalnego FK między rewards a stock_prices/stock_events; egzekwowane na poziomie widoków.
Decyzja: Złożone obliczenia portfela/historyczne są zaimplementowane jako widoki PostgreSQL zamiast w kodzie aplikacji
Dlaczego: Zmniejsza złożoność aplikacji; wykorzystuje optymalizator zapytań PostgreSQL; jedno źródło prawdy dla obliczeń
Korzyści: Spójne obliczenia we wszystkich endpointach; baza efektywnie obsługuje JOIN-y i agregacje
Kompromisy: Widoki mogą być wolne przy dużych zbiorach danych; trudniejsze w debugowaniu niż kod aplikacji; zależność od PostgreSQL
Decyzja: Użycie EXP(SUM(LN(ratio))) do obliczania skumulowanych mnożników operacji korporacyjnych
Dlaczego: Unika przepełnienia/niedomiaru zmiennoprzecinkowego przy kumulacji wielu zdarzeń
Korzyści: Stabilne numerycznie; obsługuje dowolną liczbę zdarzeń
Kompromisy: Mniej czytelne niż bezpośrednie mnożenie; wymaga dodatnich współczynników
Decyzja: Logika biznesowa znajduje się bezpośrednio w handlerach, bez osobnej warstwy usług
Dlaczego: Prostsze dla małego projektu; mniej plików i abstrakcji do utrzymania
Korzyści: Mało boilerplate'u; łatwe śledzenie przepływu żądania
Kompromisy: Trudniejsze ponowne użycie logiki; silne sprzężenie HTTP i dostępu do danych; trudniejsze niezależne testowanie logiki biznesowej
Decyzja: Użycie RandomPriceFetcher zamiast rzeczywistego API danych rynkowych
Dlaczego: Środowisko deweloperskie/testowe; brak potrzeby zarządzania kluczami API
Korzyści: Zero zewnętrznych zależności; niemal deterministyczne zachowanie
Kompromisy: Nieprzygotowane do produkcji; wartości portfela są bez znaczenia; trzeba zastąpić przy realnym wdrożeniu
Decyzja: Użycie SAVEPOINT PostgreSQL do obsługi idempotencji bez przerywania zewnętrznej transakcji
Dlaczego: Zapewnia bezpieczeństwo ponowień bez zewnętrznej usługi idempotencji
Korzyści: Zaawansowana idempotencja w transakcji działająca w ramach ograniczeń PostgreSQL
Kompromisy: Złożona obsługa błędów; narzut SAVEPOINT
Decyzja: Wszystkie endpointy są publicznie dostępne bez auth
Dlaczego: Projekt deweloperski/demo; uwierzytelnianie ma zostać dodane później (np. przez bramę API)
Korzyści: Prostsza implementacja; łatwiejsze testowanie
Kompromisy: Nieodpowiednie do produkcji; dowolny klient może tworzyć nagrody lub stosować korekty