JK
Έργα
JK
>ΦΟΡΤΩΣΗ // ΦΟΡΤΩΣΗ
ΑΡΧΙΚΟΠΟΙΗΣΗ ΠΡΟΒΟΛΗΣ
Σχεδιάστηκε και δημιουργήθηκε απόJK
JK
>ΦΟΡΤΩΣΗ // PROJECT DETAILS
PARSING ARCHITECTURE, DIAGRAMS & API ENDPOINTS
Επιστροφή στα έργα
Προβολή στο GitHub
Σύστημα Ανταμοιβών Μετοχών
Ολοκληρώθηκεgopostgresgingolang-migratevalidatorlogrusdocker

Σύστημα Ανταμοιβών Μετοχών

Λογιστικό καθολικό διπλογραφίας και backend διαχείρισης μετοχών με αποτίμηση σε INR και πλήρη έλεγχο.

Πρόβλημα & Λύση

Η διαχείριση μετοχικών δικαιωμάτων απαιτεί απόλυτη συναλλακτική ακρίβεια και ανθεκτικότητα σε διακοπές εξωτερικών πηγών τιμών.

Χαρακτηριστικά

  • Αμετάβλητο καθολικό διπλογραφικής καταχώρησης
  • Αποτίμηση σε πραγματικό χρόνο με μηχανισμούς fallback
  • Αυτοματοποιημένη κατοχύρωση (vesting) και προσαρμογές μετοχών
  • Πλήρες ιστορικό ελέγχου (audit trail)
  • Εγγύηση συναλλακτικής συνέπειας με PostgreSQL

Τεχνολογικό stack

ΤεχνολογίαΈκδοσηΣκοπόςΓιατί επιλέχθηκε
Go1.25.1Γλώσσα προγραμματισμούΙσχυρή stdlib, πρωτόγονα ταυτοχρονισμού, γρήγορη μεταγλώττιση
Gin1.11.0Πλαίσιο HTTP, δρομολόγηση, middlewareΥψηλή απόδοση, υποστήριξη middleware, ενεργή κοινότητα
PostgreSQL17Σχεσιακή βάση δεδομένωνΠροηγμένες δυνατότητες SQL (όψεις, LATERAL, παραγόμενες στήλες, ENUM)
golang-migratev4.19.0Μεταναστεύσεις βάσης δεδομένωνΑπλός εκτελεστής μεταναστεύσεων βασισμένος σε αρχεία
logrus1.9.3Δομημένη καταγραφήΔομημένα πεδία, καταγραφή με επίπεδα
go-sqlmockv1.5.2Mocking βάσης για testsΠροσομοιώνει SQL για unit tests χωρίς πραγματική βάση
lib/pqv1.10.9Driver PostgreSQLΔοκιμασμένο, εγγενής υποστήριξη PostgreSQL
Docker—ContainerizationΑναπαραγώγιμες κατασκευές, απομονωμένα περιβάλλοντα

Αρχιτεκτονική

Docker Compose

Gin HTTP Server

:8080

Handlers

Reward, Adjustment, Today, Historical, Stats, Portfolio

PriceService Worker Pool

PriceCache

in-memory RWMutex

RandomPriceFetcher

PostgreSQL 17

:5432

7 Tables

3 Views

today_rewards, historical_rewards, user_portfolio

2 ENUMs

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

Βασικά στοιχεία

  • Διακομιστής HTTP Gin με middleware RequestID για την ιχνηλάτηση αιτημάτων
  • Χειριστής ανταμοιβών με μοτίβο idempotency βασισμένο σε SAVEPOINT
  • Χειριστής προσαρμογών με κλείδωμα γραμμής SELECT FOR UPDATE
  • Χειριστές ανάγνωσης (today, historical, portfolio, stats) που εκτελούν ερωτήματα σε όψεις PostgreSQL
  • PriceService υποβάθρου με pool 5 goroutines και σημαφόρο 3 θέσεων για τη βάση

Ανθεκτικότητα συστήματος

  • PriceCache στη μνήμη με RWMutex για φόρτους με πολλές αναγνώσεις
  • Έλεγχος ταυτοχρονισμού βασισμένος σε CAS αποτρέπει αλυσιδωτές καθυστερήσεις ενημερώσεων
  • Ανάκτηση από πανικό στους workers ενημέρωσης τιμών αποτρέπει καταρρεύσεις διεργασίας
  • Ομαλός τερματισμός HTTP με χρονικό όριο αποστράγγισης 10 s
  • Περιτύλιξη συναλλαγής με αναβαλλόμενο rollback για όλες τις εγγραφές

Δομή έργου

cmd
stocky-api
main.goΣημείο εισόδου: ρύθμιση βάσης, μεταναστεύσεις, εργασία υποβάθρου, διακομιστής HTTP, ομαλός τερματισμός
internal
config
database
migrations
handlers
stocky
jobs
price_updater.goPriceService υποβάθρου με pool workers, cache, επαναλήψεις και προσομοιωμένο fetcher
storage
models
utils

API endpoints

Stocky API (θύρα 8080)

ΜέθοδοςΔιαδρομήΣκοπός
GET/healthΈλεγχος υγείας — επιστρέφει {status: OK}
POST/api/v1/rewardΔημιουργία ανταμοιβής — idempotent, συναλλακτική, υπολογισμός προμηθειών
POST/api/v1/adjustments/:idΕφαρμογή προσαρμογής — κλείδωμα γραμμής, λογιστικές εγγραφές ανά τύπο
GET/api/v1/today-stocks/:userIdΣημερινές ανταμοιβές από την όψη today_rewards
GET/api/v1/historical-inr/:userIdΙστορική αποτίμηση INR — με σελιδοποίηση, πολλαπλασιασμένη με γεγονότα
GET/api/v1/stats/:userIdΣύνοψη στατιστικών — σημερινές ανταμοιβές + συνολική αξία χαρτοφυλακίου
GET/api/v1/portfolio/:userIdΛεπτομέρειες χαρτοφυλακίου — με σελιδοποίηση, φιλτραρισμένες για διαγραφή από την αγορά

Σχεδιασμός βάσης δεδομένων

users

4 cols
idPK
serial
username
varchar
email
varchar
created_at
timestamp

rewards

6 cols
idPK
serial
user_id
int
stock_symbol
varchar
quantity
numeric(18,6)
idempotency_key
varchar
reward_date
date

ledger

6 cols
idPK
serial
reward_id
int
entry_type
ledger_entry_type
stock_symbol
varchar
quantity
numeric(18,6)
amount
numeric(18,4)

adjustments

6 cols
idPK
serial
reward_id
int
adjustment_type
varchar
delta_quantity
numeric(18,6)
delta_amount
numeric(18,4)
reason
varchar

stock_prices

4 cols
idPK
serial
stock_symbol
varchar
price
numeric(18,4)
updated_at
timestamp

stock_price_history

4 cols
idPK
serial
stock_symbol
varchar
price
numeric(18,4)
date
date

stock_events

6 cols
idPK
serial
stock_symbol
varchar
event_type
stock_event_type
ratio_num
numeric(18,6)
ratio_den
numeric(18,6)
new_symbol
varchar
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

users

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
usernameVARCHAR(50)NOT NULL, UNIQUE
emailVARCHAR(100)NOT NULL, UNIQUE
created_atTIMESTAMPΠροεπιλογή: NOW()

Λογαριασμοί χρηστών. Αρχικοποιημένοι με έναν δοκιμαστικό χρήστη.

rewards

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
user_idINTFK → users(id) ON DELETE CASCADE
stock_symbolVARCHAR(20)NOT NULL
quantityNUMERIC(18,6)NOT NULL, αρνητικό = ακύρωση
idempotency_keyVARCHAR(64)UNIQUE, επιτρέπει NULL
reward_dateDATEGENERATED ALWAYS AS (created_at::DATE) STORED

Βασικά γεγονότα ανταμοιβών. Μοναδικότητα σε (user_id, stock_symbol, reward_date) με παραλλαγή χωρίς διάκριση πεζών-κεφαλαίων.

ledger

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
reward_idINTFK → rewards(id) ON DELETE CASCADE
entry_typeledger_entry_typeENUM: stock_units, inr_outflow, fees, brokerage_fee, stt_fee, gst_fee
stock_symbolVARCHAR(20)Επιτρέπει NULL
quantityNUMERIC(18,6)Επιτρέπει NULL
amountNUMERIC(18,4)Επιτρέπει NULL

Λογιστικό βιβλίο διπλής εγγραφής. Κάθε ανταμοιβή δημιουργεί 2-5 εγγραφές.

stock_prices

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
stock_symbolVARCHAR(20)UNIQUE
priceNUMERIC(18,4)Τρέχουσα τιμή
updated_atTIMESTAMPΕνημερώνεται από το PriceService

Τελευταία γνωστή τιμή ανά μετοχή. Ενημερώνεται από το PriceService υποβάθρου.

stock_price_history

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
stock_symbolVARCHAR(20)NOT NULL
priceNUMERIC(18,4)NOT NULL
dateDATENOT NULL, UNIQUE με stock_symbol

Καθημερινά στιγμιότυπα τιμών για ιστορική αποτίμηση. Upsert από το PriceService.

stock_events

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
stock_symbolVARCHAR(20)NOT NULL
event_typestock_event_typeENUM: split, bonus, merge, delist
ratio_numNUMERIC(18,6)Αριθμητής πολλαπλασιαστή
ratio_denNUMERIC(18,6)Παρονομαστής πολλαπλασιαστή
new_symbolVARCHAR(20)Για συγχωνεύσεις
effective_dateTIMESTAMPNOT NULL

Εταιρικές ενέργειες. Το ratio_num/ratio_den ορίζει τον πολλαπλασιαστή (π.χ. split 2:1).

adjustments

ΣτήληΤύποςΣημειώσεις
idPKSERIALΔημιουργείται αυτόματα
reward_idINTFK → rewards(id) ON DELETE CASCADE
adjustment_typeVARCHAR(64)reward_reversal, fee_refund, manual_correction
delta_quantityNUMERIC(18,6)NOT NULL
delta_amountNUMERIC(18,4)NOT NULL
reasonVARCHAR(255)Επιτρέπει NULL

Τροποποιήσεις υφιστάμενων ανταμοιβών με κλείδωμα γραμμής.

Ένα-προς-πολλά: users→rewards (CASCADE), rewards→ledger (CASCADE), rewards→adjustments (CASCADE). Έμμεσες διασταυρούμενες αναφορές: rewards.stock_symbol ↔ stock_prices.stock_symbol (μέσω UPPER()), rewards.stock_symbol ↔ stock_events.stock_symbol (πολλαπλασιαστές γεγονότων). Δεν υπάρχει τυπικό FK μεταξύ rewards και stock_prices/stock_events· επιβάλλεται σε επίπεδο όψης.

Βασικές σχεδιαστικές αποφάσεις

Όψεις SQL για συγκέντρωση

Απόφαση: Οι σύνθετοι υπολογισμοί χαρτοφυλακίου/ιστορικού υλοποιούνται ως όψεις PostgreSQL αντί για κώδικα εφαρμογής

Λόγος: Μειώνει την πολυπλοκότητα της εφαρμογής· αξιοποιεί τον βελτιστοποιητή ερωτημάτων PostgreSQL· μία πηγή αλήθειας για τους υπολογισμούς

Οφέλη: Συνεπής υπολογισμός σε όλα τα endpoints· η βάση χειρίζεται αποδοτικά JOIN και συγκεντρώσεις

Ανταλλάγματα: Οι όψεις μπορεί να είναι αργές σε μεγάλα σύνολα δεδομένων· δυσκολότερος εντοπισμός σφαλμάτων από τον κώδικα εφαρμογής· εξάρτηση από PostgreSQL

Λογαριθμικό γινόμενο για πολλαπλασιαστές

Απόφαση: Χρήση EXP(SUM(LN(ratio))) για τον υπολογισμό αθροιστικών πολλαπλασιαστών εταιρικών γεγονότων

Λόγος: Αποφεύγει υπερχείλιση/υποχείλιση κινητής υποδιαστολής όταν συνδυάζονται πολλά γεγονότα

Οφέλη: Αριθμητικά σταθερό· χειρίζεται οποιονδήποτε αριθμό γεγονότων

Ανταλλάγματα: Λιγότερο ευανάγνωστο από τον άμεσο πολλαπλασιασμό· απαιτεί θετικές αναλογίες

Αρχιτεκτονική μόνο χειριστών (χωρίς επίπεδο υπηρεσίας)

Απόφαση: Η επιχειρηματική λογική βρίσκεται απευθείας στους χειριστές χωρίς ξεχωριστό επίπεδο υπηρεσίας

Λόγος: Απλούστερο για μικρό έργο· λιγότερα αρχεία και αφαιρέσεις προς συντήρηση

Οφέλη: Λίγος boilerplate κώδικας· εύκολη παρακολούθηση της ροής αιτήματος

Ανταλλάγματα: Δύσκολη επαναχρησιμοποίηση λογικής· ισχυρή σύζευξη HTTP και πρόσβασης δεδομένων· δύσκολος ανεξάρτητος έλεγχος επιχειρηματικής λογικής

Προσομοιωμένος fetcher τιμών

Απόφαση: Χρήση RandomPriceFetcher αντί για πραγματικό API δεδομένων αγοράς

Λόγος: Περιβάλλον ανάπτυξης/δοκιμών· δεν απαιτείται διαχείριση κλειδιών API

Οφέλη: Μηδενικές εξωτερικές εξαρτήσεις· σχεδόν ντετερμινιστική συμπεριφορά

Ανταλλάγματα: Όχι έτοιμο για παραγωγή· οι αξίες χαρτοφυλακίου είναι χωρίς νόημα· πρέπει να αντικατασταθεί για πραγματική ανάπτυξη

Idempotency βασισμένο σε SAVEPOINT

Απόφαση: Χρήση SAVEPOINT της PostgreSQL για διαχείριση idempotency χωρίς ματαίωση της εξωτερικής συναλλαγής

Λόγος: Επιτρέπει ασφάλεια επαναλήψεων χωρίς εξωτερική υπηρεσία idempotency

Οφέλη: Εξελιγμένο idempotency εντός συναλλαγής που λειτουργεί εντός των περιορισμών της PostgreSQL

Ανταλλάγματα: Σύνθετη διαχείριση σφαλμάτων· επιβάρυνση SAVEPOINT

Χωρίς έλεγχο ταυτότητας/εξουσιοδότηση

Απόφαση: Όλα τα endpoints είναι δημόσια προσβάσιμα χωρίς auth

Λόγος: Έργο ανάπτυξης/επίδειξης· το auth αναμένεται να προστεθεί αργότερα (π.χ. μέσω API gateway)

Οφέλη: Απλούστερη υλοποίηση· ευκολότερη δοκιμή

Ανταλλάγματα: Ακατάλληλο για παραγωγή· οποιοσδήποτε πελάτης μπορεί να δημιουργήσει ανταμοιβές ή να εφαρμόσει προσαρμογές

Δοκιμές

Τυπικό testing Go + go-sqlmock v1.5.2•19 μονάδες δοκιμών

Τι δοκιμάζεται

  • Χειριστής ανταμοιβών: idempotency NULL, μη έγκυρο JSON, μηδενική/αρνητική ποσότητα, κανονικοποίηση, idempotent επανάληψη (8 tests)
  • Χειριστής προσαρμογών: μη έγκυρο ID ανταμοιβής, μη έγκυρο JSON, μη έγκυρος τύπος, συμπεριφορά βιβλίου κατά την ακύρωση (4 tests)
  • Ιστορικός χειριστής: μη έγκυρο userId, μη έγκυρο limit (2 tests)
  • Χειριστής χαρτοφυλακίου: μη έγκυρο userId, μη έγκυρο offset, φίλτρο διαγραφής χωρίς διάκριση πεζών-κεφαλαίων (3 tests)
  • Σημερινός χειριστής: μη έγκυρο userId (1 test)
  • Χειριστής στατιστικών: μη έγκυρο userId (1 test)

Κενά κάλυψης

  • Ενσωματωμένα tests σε πραγματική PostgreSQL
  • Tests χειριστών ανάγνωσης με επαλήθευση πραγματικών δεδομένων
  • Tests του ενημερωτή τιμών υποβάθρου (διαδρομές σφαλμάτων, πανικοί, επαναλήψεις)
  • Tests ταυτόχρονης επεξεργασίας προσαρμογών
  • Tests σεναρίων rollback συναλλαγών
  • Tests ακραίων περιπτώσεων σελιδοποίησης

Βασικές προκλήσεις

  • Αποφυγή race conditions σε ταυτόχρονες οικονομικές συναλλαγές
  • Σχεδιασμός εφεδρικών μηχανισμών κατά την πτώση εξωτερικών APIs

Βασικά διδάγματα

  • Μοντελοποίηση διπλογραφίας σε σχεσιακές βάσεις δεδομένων
  • Μοτίβα κλειδώματος για ασφάλεια οικονομικών συναλλαγών