JK
>CHARGEMENT // CHARGEMENT
INITIALISATION DE LA VUE
Grand livre comptable et backend de gestion d'actions appliquant la comptabilité en partie double et la valorisation précise en INR.
La gestion d'actions requiert une rigueur transactionnelle absolue et une résilience sans faille face aux pannes des sources de prix externes.
| Technologie | Version | Objectif | Pourquoi ce choix |
|---|---|---|---|
| Go | 1.25.1 | Langage de programmation | Bibliothèque standard solide, primitives de concurrence, compilation rapide |
| Gin | 1.11.0 | Framework HTTP, routage, middleware | Haute performance, support middleware, communauté active |
| PostgreSQL | 17 | Base de données relationnelle | Fonctionnalités SQL avancées (vues, LATERAL, colonnes générées, ENUM) |
| golang-migrate | v4.19.0 | Migrations de base de données | Exécuteur de migrations simple basé sur des fichiers |
| logrus | 1.9.3 | Journalisation structurée | Champs structurés et journalisation par niveaux |
| go-sqlmock | v1.5.2 | Simulation de base de données pour tests | Simule SQL pour les tests unitaires sans BD réelle |
| lib/pq | v1.10.9 | Driver PostgreSQL | Éprouvé, support natif de PostgreSQL |
| Docker | — | Conteneurisation | Builds reproductibles, environnements isolés |
| Méthode | Route | Objectif |
|---|---|---|
| GET | /health | Vérification de l'état — renvoie {status: OK} |
| POST | /api/v1/reward | Créer une récompense — idempotent, transactionnel, calcul des frais |
| POST | /api/v1/adjustments/:id | Appliquer un ajustement — verrouillage de ligne, écritures comptables par type |
| GET | /api/v1/today-stocks/:userId | Récompenses du jour depuis la vue today_rewards |
| GET | /api/v1/historical-inr/:userId | Valorisation historique en INR — paginée, multipliée par les événements |
| GET | /api/v1/stats/:userId | Résumé des statistiques — récompenses du jour + valeur totale du portefeuille |
| GET | /api/v1/portfolio/:userId | Détails du portefeuille — paginés, filtrés par retrait de cote |
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| username | VARCHAR(50) | NOT NULL, UNIQUE |
| VARCHAR(100) | NOT NULL, UNIQUE | |
| created_at | TIMESTAMP | Par défaut : NOW() |
Comptes utilisateurs. Initialisés avec un utilisateur de test.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| user_id | INT | FK → users(id) ON DELETE CASCADE |
| stock_symbol | VARCHAR(20) | NOT NULL |
| quantity | NUMERIC(18,6) | NOT NULL, négatif = annulation |
| idempotency_key | VARCHAR(64) | UNIQUE, autorise NULL |
| reward_date | DATE | GENERATED ALWAYS AS (created_at::DATE) STORED |
Événements de récompense principaux. Unique sur (user_id, stock_symbol, reward_date) avec variante insensible à la casse.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| 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) | Autorise NULL |
| quantity | NUMERIC(18,6) | Autorise NULL |
| amount | NUMERIC(18,4) | Autorise NULL |
Grand livre en partie double. Chaque récompense génère 2 à 5 écritures.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| stock_symbol | VARCHAR(20) | UNIQUE |
| price | NUMERIC(18,4) | Prix actuel |
| updated_at | TIMESTAMP | Mis à jour par PriceService |
Dernier prix connu par action. Mis à jour par le PriceService en arrière-plan.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| stock_symbol | VARCHAR(20) | NOT NULL |
| price | NUMERIC(18,4) | NOT NULL |
| date | DATE | NOT NULL, UNIQUE avec stock_symbol |
Instantanés quotidiens des prix pour la valorisation historique. Upsertés par PriceService.
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| stock_symbol | VARCHAR(20) | NOT NULL |
| event_type | stock_event_type | ENUM : split, bonus, merge, delist |
| ratio_num | NUMERIC(18,6) | Numérateur du multiplicateur |
| ratio_den | NUMERIC(18,6) | Dénominateur du multiplicateur |
| new_symbol | VARCHAR(20) | Pour les fusions |
| effective_date | TIMESTAMP | NOT NULL |
Opérations sur titres. ratio_num/ratio_den définit le multiplicateur (ex. : split 2:1).
| Colonne | Type | Notes |
|---|---|---|
| idPK | SERIAL | Généré automatiquement |
| 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) | Autorise NULL |
Modifications des récompenses existantes avec verrouillage au niveau ligne.
Un-à-plusieurs : users→rewards (CASCADE), rewards→ledger (CASCADE), rewards→adjustments (CASCADE). Références croisées implicites : rewards.stock_symbol ↔ stock_prices.stock_symbol (via UPPER()), rewards.stock_symbol ↔ stock_events.stock_symbol (multiplicateurs d'événements). Aucune FK formelle entre rewards et stock_prices/stock_events ; contrainte appliquée au niveau des vues.
Décision : Les calculs complexes de portefeuille/historiques sont implémentés sous forme de vues PostgreSQL plutôt que dans le code applicatif
Pourquoi : Réduit la complexité applicative ; exploite l'optimiseur de requêtes PostgreSQL ; source unique de vérité pour les calculs
Avantages : Calcul cohérent sur tous les endpoints ; la base gère efficacement les JOIN et agrégations
Compromis : Les vues peuvent être lentes sur de gros volumes ; plus difficiles à déboguer que le code applicatif ; dépendance à PostgreSQL
Décision : Utiliser EXP(SUM(LN(ratio))) pour calculer les multiplicateurs cumulés des opérations sur titres
Pourquoi : Évite le dépassement/sous-dépassement en virgule flottante lorsque de nombreux événements se cumulent
Avantages : Numériquement stable ; gère un nombre arbitraire d'événements
Compromis : Moins lisible que la multiplication directe ; nécessite des ratios positifs
Décision : La logique métier vit directement dans les gestionnaires, sans couche de service séparée
Pourquoi : Plus simple pour un petit projet ; moins de fichiers et d'abstractions à maintenir
Avantages : Peu de code répétitif ; flux de requête facile à suivre
Compromis : Logique difficile à réutiliser ; couplage fort entre HTTP et accès aux données ; tests métier indépendants difficiles
Décision : Utiliser RandomPriceFetcher au lieu d'une API de données de marché réelle
Pourquoi : Environnement de développement/test ; aucune gestion de clé API nécessaire
Avantages : Aucune dépendance externe ; comportement quasi déterministe
Compromis : Pas prêt pour la production ; les valeurs du portefeuille n'ont pas de sens ; à remplacer pour un déploiement réel
Décision : Utiliser les SAVEPOINT PostgreSQL pour gérer l'idempotence sans annuler la transaction externe
Pourquoi : Permet une sécurité de reprise sans service d'idempotence externe
Avantages : Idempotence avancée intra-transaction compatible avec les contraintes PostgreSQL
Compromis : Gestion d'erreurs complexe ; surcoût des SAVEPOINT
Décision : Tous les endpoints sont accessibles publiquement sans authentification
Pourquoi : Projet de développement/démo ; authentification prévue ultérieurement (ex. : via une passerelle API)
Avantages : Implémentation plus simple ; tests plus faciles
Compromis : Pas adapté à la production ; tout client peut créer des récompenses ou appliquer des ajustements