JK
>LADEN // LADEN
WEERGAVE INITIALISEREN
Financieel grootboek en aandelenbeloningssysteem met dubbel boekhouden en nauwkeurige INR-waardebepaling.
Aandelenbeloningen vereisen strikte transactionele nauwkeurigheid en fouttolerantie bij uitval van externe beurs-API's.
| Technologie | Versie | Doel | Waarom gekozen |
|---|---|---|---|
| Go | 1.25.1 | Programmeertaal | Sterke stdlib, concurrency-primitieven, snelle compilatie |
| Gin | 1.11.0 | HTTP-framework, routing, middleware | Hoge prestaties, middleware-ondersteuning, actieve community |
| PostgreSQL | 17 | Relationele database | Geavanceerde SQL-functies (views, LATERAL, generated columns, ENUMs) |
| golang-migrate | v4.19.0 | Databasemigraties | Eenvoudige bestandsgebaseerde migratierunner |
| logrus | 1.9.3 | Gestructureerde logging | Gestructureerde velden, logging met niveaus |
| go-sqlmock | v1.5.2 | Database-mocking voor tests | Simuleert SQL voor unit tests zonder echte DB |
| lib/pq | v1.10.9 | PostgreSQL-driver | Beproefd, native PostgreSQL-ondersteuning |
| Docker | — | Containerisatie | Reproduceerbare builds, geïsoleerde omgevingen |
| Methode | Route | Doel |
|---|---|---|
| GET | /health | Healthcheck — retourneert {status: OK} |
| POST | /api/v1/reward | Reward aanmaken — idempotent, transactioneel, kostenberekening |
| POST | /api/v1/adjustments/:id | Adjustment toepassen — rijvergrendeling, ledgerposten per type |
| GET | /api/v1/today-stocks/:userId | Rewards van vandaag uit de view today_rewards |
| GET | /api/v1/historical-inr/:userId | Historische INR-waardering — gepagineerd, vermenigvuldigd met events |
| GET | /api/v1/stats/:userId | Statistiekenoverzicht — rewards van vandaag + totale portefeuillewaarde |
| GET | /api/v1/portfolio/:userId | Portefeuilledetails — gepagineerd, gefilterd op delisting |
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| username | VARCHAR(50) | NOT NULL, UNIQUE |
| VARCHAR(100) | NOT NULL, UNIQUE | |
| created_at | TIMESTAMP | Standaard: NOW() |
Gebruikersaccounts. Gevuld met één testgebruiker.
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| user_id | INT | FK → users(id) ON DELETE CASCADE |
| stock_symbol | VARCHAR(20) | NOT NULL |
| quantity | NUMERIC(18,6) | NOT NULL, negatief = terugdraaien |
| idempotency_key | VARCHAR(64) | UNIQUE, staat NULL toe |
| reward_date | DATE | GENERATED ALWAYS AS (created_at::DATE) STORED |
Kern-rewardevents. Uniek op (user_id, stock_symbol, reward_date) met hoofdletterongevoelige variant.
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| 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) | NULL toegestaan |
| quantity | NUMERIC(18,6) | NULL toegestaan |
| amount | NUMERIC(18,4) | NULL toegestaan |
Ledger volgens dubbel boekhouden. Elke reward genereert 2-5 posten.
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| stock_symbol | VARCHAR(20) | UNIQUE |
| price | NUMERIC(18,4) | Huidige prijs |
| updated_at | TIMESTAMP | Bijgewerkt door PriceService |
Laatst bekende prijs per aandeel. Bijgewerkt door achtergrond-PriceService.
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| stock_symbol | VARCHAR(20) | NOT NULL |
| price | NUMERIC(18,4) | NOT NULL |
| date | DATE | NOT NULL, UNIQUE met stock_symbol |
Dagelijkse prijssnapshots voor historische waardering. Upsert door PriceService.
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| stock_symbol | VARCHAR(20) | NOT NULL |
| event_type | stock_event_type | ENUM: split, bonus, merge, delist |
| ratio_num | NUMERIC(18,6) | Teller van multiplier |
| ratio_den | NUMERIC(18,6) | Noemer van multiplier |
| new_symbol | VARCHAR(20) | Voor fusies |
| effective_date | TIMESTAMP | NOT NULL |
Corporate actions. ratio_num/ratio_den definieert de multiplier (bijv. 2:1 split).
| Kolom | Type | Notities |
|---|---|---|
| idPK | SERIAL | Automatisch gegenereerd |
| 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) | NULL toegestaan |
Wijzigingen aan bestaande rewards met rijvergrendeling.
Een-op-veel: users→rewards (CASCADE), rewards→ledger (CASCADE), rewards→adjustments (CASCADE). Impliciete kruisverwijzingen: rewards.stock_symbol ↔ stock_prices.stock_symbol (via UPPER()), rewards.stock_symbol ↔ stock_events.stock_symbol (eventmultiplicatoren). Geen formele FK tussen rewards en stock_prices/stock_events; afgedwongen op viewniveau.
Beslissing: Complexe portefeuille-/historische berekeningen zijn geïmplementeerd als PostgreSQL-views in plaats van applicatiecode
Waarom: Vermindert applicatiecomplexiteit; benut de PostgreSQL-queryoptimizer; één bron van waarheid voor berekeningen
Voordelen: Consistente berekening op alle endpoints; de database verwerkt JOINs en aggregaties efficiënt
Afwegingen: Views kunnen traag zijn bij grote datasets; moeilijker te debuggen dan applicatiecode; gebonden aan PostgreSQL
Beslissing: EXP(SUM(LN(ratio))) gebruiken om cumulatieve corporate-actionmultiplicatoren te berekenen
Waarom: Voorkomt floating-point overflow/underflow wanneer veel events samenkomen
Voordelen: Numeriek stabiel; verwerkt een willekeurig aantal events
Afwegingen: Minder leesbaar dan direct vermenigvuldigen; vereist positieve ratio's
Beslissing: Bedrijfslogica staat direct in handlers zonder aparte servicelaag
Waarom: Eenvoudiger voor een klein project; minder bestanden en abstracties te onderhouden
Voordelen: Weinig boilerplate; requestflow eenvoudig te volgen
Afwegingen: Logica moeilijk te hergebruiken; sterke koppeling tussen HTTP en data-access; bedrijfslogica lastig afzonderlijk te testen
Beslissing: RandomPriceFetcher gebruiken in plaats van een echte marktdata-API
Waarom: Ontwikkel-/testomgeving; geen API-sleutelbeheer nodig
Voordelen: Geen externe afhankelijkheden; vrijwel deterministisch gedrag
Afwegingen: Niet productieklaar; portefeuillewaarden zijn betekenisloos; moet worden vervangen voor echte deployment
Beslissing: PostgreSQL SAVEPOINTs gebruiken om idempotentie af te handelen zonder de buitenste transactie af te breken
Waarom: Maakt retry-veiligheid mogelijk zonder externe idempotentieservice
Voordelen: Geavanceerde idempotentie binnen de transactie die binnen PostgreSQL-beperkingen werkt
Afwegingen: Complexe foutafhandeling; SAVEPOINT-overhead
Beslissing: Alle endpoints zijn openbaar toegankelijk zonder auth
Waarom: Ontwikkel-/demoproject; auth wordt later toegevoegd (bijv. via API-gateway)
Voordelen: Eenvoudigere implementatie; eenvoudiger testen
Afwegingen: Niet geschikt voor productie; elke client kan rewards aanmaken of adjustments toepassen