JK
>LADDAR // LADDAR
INITIERAR VY
Finansiell huvudbok och aktiebelöningsbackend med dubbel bokföring och exakt INR-värdering.
Hantering av aktiebelöningar kräver strikt transaktionsnoggrannhet och feltolerans vid externa API-avbrott.
| Teknik | Version | Syfte | Varför vald |
|---|---|---|---|
| Go | 1.25.1 | Programmeringsspråk | Stark stdlib, concurrency-primitiver, snabb kompilering |
| Gin | 1.11.0 | HTTP-ramverk, routing, middleware | Hög prestanda, middleware-stöd, aktiv community |
| PostgreSQL | 17 | Relationsdatabas | Avancerade SQL-funktioner (vyer, LATERAL, genererade kolumner, ENUM) |
| golang-migrate | v4.19.0 | Databasmigreringar | Enkel filbaserad migrationskörare |
| logrus | 1.9.3 | Strukturerad loggning | Strukturerade fält, nivåbaserad loggning |
| go-sqlmock | v1.5.2 | Databasmockning för tester | Simulerar SQL för enhetstester utan riktig DB |
| lib/pq | v1.10.9 | PostgreSQL-drivrutin | Beprövad, inbyggt PostgreSQL-stöd |
| Docker | — | Containerisering | Reproducerbara byggen, isolerade miljöer |
| Metod | Rutt | Syfte |
|---|---|---|
| GET | /health | Hälsokontroll — returnerar {status: OK} |
| POST | /api/v1/reward | Skapa belöning — idempotent, transaktionell, avgiftsberäkning |
| POST | /api/v1/adjustments/:id | Tillämpa justering — radlåsning, redovisningsposter per typ |
| GET | /api/v1/today-stocks/:userId | Dagens belöningar från vyn today_rewards |
| GET | /api/v1/historical-inr/:userId | Historisk INR-värdering — paginerad, händelsemultiplicerad |
| GET | /api/v1/stats/:userId | Statistiköversikt — dagens belöningar + totalt portföljvärde |
| GET | /api/v1/portfolio/:userId | Portföljdetaljer — paginerade, filtrerade för avnotering |
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| username | VARCHAR(50) | NOT NULL, UNIQUE |
| VARCHAR(100) | NOT NULL, UNIQUE | |
| created_at | TIMESTAMP | Standard: NOW() |
Användarkonton. Seedade med en testanvändare.
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| user_id | INT | FK → users(id) ON DELETE CASCADE |
| stock_symbol | VARCHAR(20) | NOT NULL |
| quantity | NUMERIC(18,6) | NOT NULL, negativt = återföring |
| idempotency_key | VARCHAR(64) | UNIQUE, tillåter NULL |
| reward_date | DATE | GENERATED ALWAYS AS (created_at::DATE) STORED |
Kärnbelöningsevenemang. Unikt på (user_id, stock_symbol, reward_date) med skiftlägesokänslig variant.
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| 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) | Tillåter NULL |
| quantity | NUMERIC(18,6) | Tillåter NULL |
| amount | NUMERIC(18,4) | Tillåter NULL |
Redovisningsbok med dubbel bokföring. Varje belöning genererar 2–5 poster.
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| stock_symbol | VARCHAR(20) | UNIQUE |
| price | NUMERIC(18,4) | Aktuellt pris |
| updated_at | TIMESTAMP | Uppdateras av PriceService |
Senast kända pris per aktie. Uppdateras av bakgrunds-PriceService.
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| stock_symbol | VARCHAR(20) | NOT NULL |
| price | NUMERIC(18,4) | NOT NULL |
| date | DATE | NOT NULL, UNIQUE med stock_symbol |
Dagliga prisögonblicksbilder för historisk värdering. Upsertas av PriceService.
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| stock_symbol | VARCHAR(20) | NOT NULL |
| event_type | stock_event_type | ENUM: split, bonus, merge, delist |
| ratio_num | NUMERIC(18,6) | Multiplikatorns täljare |
| ratio_den | NUMERIC(18,6) | Multiplikatorns nämnare |
| new_symbol | VARCHAR(20) | För fusioner |
| effective_date | TIMESTAMP | NOT NULL |
Företagshändelser. ratio_num/ratio_den definierar multiplikatorn (t.ex. 2:1-split).
| Kolumn | Typ | Anteckningar |
|---|---|---|
| idPK | SERIAL | Genereras automatiskt |
| 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) | Tillåter NULL |
Ändringar av befintliga belöningar med radlåsning.
En-till-många: users→rewards (CASCADE), rewards→ledger (CASCADE), rewards→adjustments (CASCADE). Implicita korsreferenser: rewards.stock_symbol ↔ stock_prices.stock_symbol (via UPPER()), rewards.stock_symbol ↔ stock_events.stock_symbol (händelsemultiplikatorer). Ingen formell FK mellan rewards och stock_prices/stock_events; upprätthålls på vynivå.
Beslut: Komplexa portfölj-/historiska beräkningar implementeras som PostgreSQL-vyer i stället för applikationskod
Varför: Minskar applikationskomplexiteten; utnyttjar PostgreSQLs frågeoptimerare; en enda sanningskälla för beräkningar
Fördelar: Konsekvent beräkning över alla endpoints; databasen hanterar JOIN och aggregering effektivt
Avvägningar: Vyer kan vara långsamma för stora datamängder; svårare att felsöka än applikationskod; bundet till PostgreSQL
Beslut: Använd EXP(SUM(LN(ratio))) för att beräkna kumulativa multiplikatorer för företagshändelser
Varför: Undviker flyttalsöverflöd/underflöde när många händelser kombineras
Fördelar: Numeriskt stabilt; hanterar ett godtyckligt antal händelser
Avvägningar: Mindre läsbart än direkt multiplikation; kräver positiva förhållanden
Beslut: Affärslogiken ligger direkt i handlers utan ett separat servicelager
Varför: Enklare för ett litet projekt; färre filer och abstraktioner att underhålla
Fördelar: Lite boilerplate; lätt att följa förfrågningsflödet
Avvägningar: Svårare att återanvända logik; hård koppling mellan HTTP och dataåtkomst; svårare att testa affärslogik separat
Beslut: Använd RandomPriceFetcher i stället för ett verkligt marknadsdata-API
Varför: Utvecklings-/testmiljö; ingen API-nyckelhantering behövs
Fördelar: Inga externa beroenden; nästan deterministiskt beteende
Avvägningar: Inte produktionsklar; portföljvärden är meningslösa; måste ersättas för verklig driftsättning
Beslut: Använd PostgreSQL SAVEPOINTs för att hantera idempotens utan att avbryta den yttre transaktionen
Varför: Möjliggör retry-säkerhet utan extern idempotens-tjänst
Fördelar: Avancerad idempotens inom transaktionen som fungerar inom PostgreSQLs begränsningar
Avvägningar: Komplex felhantering; SAVEPOINT-overhead
Beslut: Alla endpoints är offentligt åtkomliga utan auth
Varför: Utvecklings-/demoprojekt; auth förväntas läggas till senare (t.ex. via API-gateway)
Fördelar: Enklare implementering; enklare testning
Avvägningar: Inte lämpligt för produktion; vilken klient som helst kan skapa belöningar eller tillämpa justeringar