Comment nous avons construit une plateforme multi-apps avec une seule codebase
Alphazed exploite plus de 7 applications éducatives (Amal, Thurayya, Qais, KidElite, Alphazed School, Alphazed Montessori, et plus) à partir d'une seule base de code backend et d'un cadre mobile Flutter partagé. Chaque application dispose de ses propres tables de base de données (préfixées), configuration, notifications push, modèles d'email, et contenu — mais partage l'authentification (AWS Cognito), l'infrastructure d'analytique et les algorithmes d'apprentissage de base.
Backend : Sélection d'application à l'exécution
Comment ça marche
Lors du déploiement, une variable d'environnement sélectionne l'application :
# Déployer Amal
export APP_NAME=amal
serverless deploy
# Déployer Thurayya
export APP_NAME=thurayya
serverless deploy
# Déployer Qais
export APP_NAME=qais
serverless deploy
Chaque déploiement crée des fonctions Lambda indépendantes, des routes API Gateway, et une surveillance — mais elles se connectent toutes à la même base de code backend.
Priorité de configuration à trois niveaux
# src/config.py
import os
app_name = os.getenv('APP_NAME', 'amal')
# Niveau 1 : Correspondance exacte de l'application
config = load_json(f'config/{app_name}.json')
# Niveau 2 : Correspondance de la famille d'applications (si la configuration exacte n'existe pas)
if not config:
family = app_name.split('_')[0] # 'amal_beta' → 'amal'
config = load_json(f'config/{family}.json')
# Niveau 3 : Défaut
if not config:
config = load_json('config/default.json')
Configuration par application (config/amal.json)
{
"app_name": "amal",
"app_id": "com.alphazed.amal",
"database": {
"table_prefix": "amal_"
},
"email": {
"template_dir": "templates/amal",
"from_address": "amal@alphazed.com",
"from_name": "Équipe Amal"
},
"push_notifications": {
"firebase_project": "alphazed-amal",
"apns_certificate": "certs/amal.pem"
},
"content": {
"s3_bucket": "amal-content-production",
"curriculum_id": "amal_v3_arabic"
},
"feature_flags": {
"enable_tajweed_feedback": false,
"enable_noorani_qaida": false,
"enable_juz_amma": false,
"enable_creature_building": true,
"enable_physics_games": true
},
"pricing": {
"monthly_usd": 6.99,
"yearly_usd": 67.99,
"trial_days": 14
}
}
Ressources partagées vs par application
| Ressource | Partagée | Par application | Raison |
|---|---|---|---|
| Code Lambda | ✓ Oui | ✗ Non | Codebase unique, bifurcation par APP_NAME |
| Instance RDS | ✓ Oui | ✗ Non (tables préfixées) | Réduction des coûts, sauvegarde unique |
| Contenu S3 | ✗ Non | ✓ Oui | Chaque app a son propre programme |
| AWS Cognito | ✓ Oui | ✗ Non (via app_id) | Authentification centrale, différenciation par app_id |
| Lac d'analytique | ✓ Oui | ✗ Non (partitionné par app) | Pipeline unifié, données séparées |
| Messagerie Firebase | ✗ Non | ✓ Oui | APNs spécifiques à l'app + FCM |
Frontend : Architecture multi-apps Flutter
Noyau partagé (packages/alphazed_common)
- Gestion d'état (Riverpod)
- Gestion audio
- Bibliothèque d'animation (intégration Rive)
- Système de design (couleurs, typographie, widgets)
- Flux d'authentification
- Client d'analytique
Couches spécifiques aux applications (apps/amal, apps/thurayya, etc.)
apps/
├── amal/
│ ├── lib/
│ │ ├── main.dart (point d'entrée de l'app)
│ │ ├── config/
│ │ │ ├── curriculum.json (structure des leçons d'Amal)
│ │ │ ├── colors.dart (thème d'Amal)
│ │ │ └── characters.json (personnalisation d'avatar)
│ │ └── screens/ (écrans spécifiques à Amal)
│ └── pubspec.yaml (dépendances d'Amal)
│
├── thurayya/
│ ├── lib/
│ │ ├── main.dart (point d'entrée différent)
│ │ ├── config/
│ │ │ ├── curriculum.json (structure de Juz Amma)
│ │ │ ├── colors.dart (thème de Thurayya)
│ │ │ └── characters.json (personnalisation d'avatar)
│ │ └── screens/ (écrans spécifiques à Thurayya)
│ └── pubspec.yaml (dépendances de Thurayya)
│
└── packages/
└── alphazed_common/ (code partagé)
Configuration au moment de la construction
# Construire Amal
flutter build apk --dart-define=APP_NAME=amal --dart-define=CURRICULUM=amal_v3
# Construire Thurayya
flutter build apk --dart-define=APP_NAME=thurayya --dart-define=CURRICULUM=thurayya_juzamma
Au moment de la construction, chaque application reçoit ses propres données de programme, couleurs et configuration intégrées. Un seul dépôt de code, plusieurs binaires compilés.
Déploiement : Piles indépendantes
Configuration du Framework Serverless
# serverless.yml
service: alphazed-backend
custom:
pythonRequirements:
dockerizePip: true
app_name: ${env:APP_NAME}
functions:
# Ces fonctions sont déployées pour chaque valeur APP_NAME
get_user:
handler: src/handlers/user.get_user
events:
- http:
path: app/{app_name}/user/{user_id}
method: get
content_duo:
handler: src/handlers/content.generate_content_duo
timeout: 20
events:
- http:
path: app/{app_name}/content_duo
method: post
resources:
Resources:
# Chaque application reçoit son propre groupe de logs CloudWatch
${self:custom.app_name}LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/lambda/alphazed-${self:custom.app_name}
RetentionInDays: 30
Lorsque vous déployez avec APP_NAME=amal, CloudFormation crée :
- Fonctions Lambda préfixées
alphazed-amal-* - Routes API Gateway sous
/amal/* - Logs CloudWatch sous
/aws/lambda/alphazed-amal - Évolutivité et surveillance indépendantes
Pourquoi c'est important
Pour les équipes produits
- Lancement de nouvelle app : semaines au lieu de mois (infrastructure partagée)
- Parité de fonctionnalités : corrections de bugs et améliorations d'algorithmes appliquées instantanément à toutes les apps
- Test A/B : tester facilement des fonctionnalités sur une app avant de les déployer sur d'autres
Pour l'ingénierie
- Codebase unique : un seul ensemble de tests, un seul pipeline CI/CD
- Algorithmes d'apprentissage partagés : la répétition espacée et le mixage de contenu profitent à toutes les apps
- Efficacité opérationnelle : une équipe gère toute l'infrastructure
Pour les coûts
- RDS partagé : une fraction du coût de 7 bases de données indépendantes
- Cognito partagé : un fournisseur d'authentification pour toutes les apps
- Analytique partagée : un pipeline desservant 7 apps
- Économies estimées : 40 000 à 60 000 $/an par rapport à des backends séparés
Défis et solutions
Défi 1 : Conflits de schéma de base de données
- Amal nécessite la table
amal_user_memory - Thurayya nécessite la table
thurayya_user_memory(champs différents pour la mémorisation du Coran) - Solution : les migrations tiennent compte des apps.
migrations/001_amal_user_memory.sqlne s'exécute que lorsqueAPP_NAME=amal
Défi 2 : Différents ensembles de fonctionnalités
- Thurayya a des retours de tajweed ; Amal non
- Amal a des jeux de physique ; Thurayya non
- Solution : drapeaux de fonctionnalités dans la configuration (
enable_tajweed_feedback,enable_physics_games)
Défi 3 : Évolutivité indépendante
- Amal voit un pic de trafic ; Thurayya est calme
- Piscine Lambda partagée signifie qu'elles se disputent la concurrence
- Solution : politiques de mise à l'échelle CloudWatch par application. Si les Lambdas
amal-*atteignent la limite de concurrence, mise à l'échelle automatique indépendamment
FAQ
Q : Le partage d'une seule base de code ne crée-t-il pas de couplage entre les apps ?
R : Non. Le code de chaque app est dans des répertoires séparés (apps/amal, apps/thurayya). Le code partagé est dans packages/alphazed_common et est explicitement versionné. Le couplage fort est une mauvaise odeur de conception que nous détectons lors de la revue de code.
Q : Que se passe-t-il si une app nécessite un changement d'API incompatible ?
R : Nous versionnons les APIs par app : /amal/v1/*, /thurayya/v1/*. Les apps peuvent évoluer indépendamment. Les anciennes versions fonctionnent pendant 12 mois, ce qui laisse le temps aux équipes d'apps de se mettre à jour.
Q : Les apps peuvent-elles partager des utilisateurs ? R : Pas par défaut. Chaque app a sa propre table utilisateur (préfixée). Si un parent veut s'abonner à la fois à Amal et Thurayya, il crée des comptes séparés (ou nous pourrions ajouter une fonctionnalité de liaison "familiale" plus tard).



