Symfony 8 en architecture hexagonale, PostgreSQL multi-tenant, agent IA Claude avec function calling, et communication SMS bidirectionnelle.
Separation stricte Domain / Application / Infrastructure.
Entites metier pures (Telemetry, Device, Trip, User), value objects, interfaces de repositories. Aucune dependance framework.
Coeur metierUse cases / services applicatifs : IngestTelemetry, StartTrip, AskJarvis, SendCommand. Orchestration entre le domaine et les ports.
Use CasesAdaptateurs concrets : Doctrine ORM (PostgreSQL), Mercure publisher, Claude API client, SQS queue consumer, SMS gateway. Implementation des ports definis par le domaine.
AdaptateursPoints d'entree principaux de l'application.
| Methode | Endpoint | Description | Auth |
|---|---|---|---|
POST | /api/telemetry | Reception des donnees du module 4G (ingestion via queue en phase scale) | X-Device-Token |
GET | /api/telemetry/{device}/latest | Derniere telemetrie connue | JWT |
GET | /api/telemetry/{device}/history | Historique avec filtres temporels | JWT |
GET | /api/trips | Liste des trajets enregistres (avec trace PostGIS) | JWT |
POST | /api/command | Envoyer une commande au module (fibre SUNKENET, reboot...) | JWT |
POST | /api/jarvis/chat | Envoyer une question a l'agent IA Jarvis | JWT |
GET | /api/jarvis/actions | Historique des actions executees par Jarvis | JWT |
POST | /webhook/sms | Reception SMS entrant (webhook modem) | X-Device-Token |
GET | /api/fuel/stations | Stations essence proches (proxy prix-carburants.gouv.fr) | JWT |
GET | /api/firmware/latest | Version firmware disponible pour OTA | X-Device-Token |
Schema optimise pour les series temporelles vehiculaires avec isolation multi-tenant.
Le modele de donnees repose sur cinq tables principales :
| Table | Role | Particularites |
|---|---|---|
tenants | Isolation multi-tenant | Chaque organisation ou utilisateur possede un tenant avec un plan (free, pro, etc.) |
users | Comptes utilisateurs | Rattaches a un tenant, authentification par email/mot de passe, roles JSONB |
devices | Modules IoT enregistres | Chaque device est lie a un tenant et un proprietaire, avec un token d'authentification hache (bcrypt) |
telemetry | Donnees capteurs en serie temporelle | Partitionnee par mois, colonne JSONB flexible pour tous les capteurs OBD, index GiST (geographie) et BRIN (temporel) |
trips | Trajets enregistres | Trace GPS (PostGIS LineString), statistiques agregees (vitesse moy, RPM moy, conso, boost max) |
JSONB flexible : La colonne data accepte tous les capteurs OBD (standard + BMW UDS) sans migration de schema. Exemple M57 : {"rpm": 3000, "speed": 120, "coolant_temp": 95, "oil_temp": 105, "rail_pressure": 1350, "dpf_soot": 12.5, "turbo_boost": 1.8}
Multi-tenant : Toutes les requetes Doctrine incluent un filtre tenant_id automatique via un Doctrine Filter global. Un utilisateur ne peut jamais acceder aux donnees d'un autre tenant.
Agent autonome Claude avec function calling. Remplace OpenAI — licence entreprise MAX.
Jarvis n'est pas un simple chatbot. C'est un agent autonome qui dispose d'un knowledge graph sur l'utilisateur (habitudes, lieux frequents, horaires) et de tools qu'il peut invoquer de maniere autonome via le protocole function calling de Claude.
Symfony construit un contexte structure avant chaque requete a Claude, incluant l'etat du vehicule en temps reel (RPM, temperatures, pressions, localisation), les alertes actives, et les habitudes de l'utilisateur (lieux frequents, station essence habituelle, horaires de trajet).
| Tool | Parametres | Effet |
|---|---|---|
change_fiber_color | color: string | Change la couleur du kit fibre optique SUNKENET |
search_fuel_station | fuel_type, radius_km | Recherche stations diesel pas cheres (prix-carburants.gouv.fr) |
send_sms | to, body | Envoie un SMS via le modem 4G |
send_push_notification | message, level | Notification push sur le dashboard React |
read_email | count | Lit les derniers emails (Gmail/Outlook API) |
check_calendar | date | Consulte le calendrier Google Calendar |
calculate_route | destination | Calcul d'itineraire optimise |
set_monitoring_freq | pid, hz | Ajuste la frequence de lecture d'un capteur OBD |
Securite IA : Toutes les function calls retournees par Claude passent par un validateur Symfony (Application/Validator/ToolCallValidator) avant execution. L'IA ne peut pas envoyer de commandes arbitraires au vehicule. Les tools autorises sont definis par la configuration du tenant.
Ingestion asynchrone pour absorber les pics de telemetrie.
Le controller /api/telemetry valide le token, puis pousse le payload dans la queue SQS au lieu de persister directement.
Un worker Symfony Messenger consomme la queue, persiste en PostgreSQL et publie sur Mercure. Plusieurs workers en parallele sur ECS/Fargate.
Apres persistance, un second handler analyse les tendances. Si anomalie detectee (temperature huile M57 trop haute, DPF sature), declenchement d'une alerte via Jarvis.
Phase 1 (MVP) : ingestion synchrone directe en PostgreSQL. La queue SQS sera activee en Phase 4 quand le nombre de devices augmentera significativement.
Publication serveur-vers-client sans polling.
A chaque reception de telemetrie, l'API Symfony valide le X-Device-Token, persiste les donnees en PostgreSQL (RPM, temperature huile, pression common rail, DPF, turbo...), puis publie une mise a jour Mercure sur le topic du device. Le dashboard React recoit instantanement les nouvelles valeurs via Server-Sent Events.
Interaction bidirectionnelle par SMS. Reboot module a distance, alertes urgentes.
Le modem 4G recoit un SMS et le transmet a Symfony via POST /webhook/sms.
Symfony verifie si le numero expediteur est dans la liste blanche du tenant.
Si commande systeme (ex: "REBOOT") : execution directe. Sinon : envoi a Jarvis avec le contexte vehiculaire.
Symfony commande au modem 4G d'envoyer le SMS de reponse. Cas d'usage : alerte station essence pas chere, alerte vehicule, confirmation reboot.
Authentification multi-couche et isolation des donnees.
Chaque module IoT possede un X-Device-Token unique. Le hash bcrypt est stocke en base (table devices). Token en NVS chiffre sur l'ESP32. Validation a chaque requete entrante.
Les endpoints dashboard sont proteges par JWT (LexikJWTAuthenticationBundle). Token renouvele via refresh token. Le JWT contient le tenant_id pour le filtrage automatique.
AuthLe module ESP32 valide l'empreinte du certificat SSL du serveur pour empecher les attaques MITM. Certificat stocke dans la partition data de l'ESP32.
TLSToutes les function calls de Claude sont validees par un ToolCallValidator Symfony avant execution. Les tools autorises sont configures par tenant. Logging complet de chaque action IA.
AI SafetyIsolation multi-tenant : Un Doctrine Filter global injecte automatiquement le tenant_id dans chaque requete SQL. Impossible pour un utilisateur d'acceder aux donnees d'un autre tenant, meme en cas de bug applicatif.
Arborescence complete du code backend avec explication de chaque couche et regles de dependance.
src/
├── Domain/ # Coeur metier — ZERO dependance externe
│ ├── Entity/
│ │ ├── Telemetry.php # Entite telemetrie (RPM, temp, GPS...)
│ │ ├── Device.php # Module IoT enregistre
│ │ ├── Trip.php # Trajet enregistre
│ │ ├── User.php # Utilisateur du dashboard
│ │ └── Tenant.php # Organisation multi-tenant
│ ├── ValueObject/
│ │ ├── DeviceToken.php # Value object token device (immutable)
│ │ ├── GpsCoordinate.php # Latitude + longitude
│ │ ├── TelemetryData.php # Donnees capteurs typees
│ │ └── FiberColor.php # Couleur RGBW fibre SUNKENET
│ ├── Port/
│ │ ├── TelemetryRepositoryInterface.php
│ │ ├── DeviceRepositoryInterface.php
│ │ ├── TripRepositoryInterface.php
│ │ ├── AiAgentInterface.php # Port vers Claude API
│ │ ├── RealtimePublisherInterface.php # Port vers Mercure
│ │ └── SmsGatewayInterface.php # Port vers passerelle SMS
│ └── Exception/
│ ├── DeviceNotFoundException.php
│ └── UnauthorizedDeviceException.php
│
├── Application/ # Use cases — depend UNIQUEMENT de Domain
│ ├── UseCase/
│ │ ├── IngestTelemetry.php # Reception + persistance + publish Mercure
│ │ ├── StartTrip.php # Demarrage d'un nouveau trajet
│ │ ├── EndTrip.php # Fin de trajet + calcul stats
│ │ ├── AskJarvis.php # Envoi question a l'agent IA
│ │ ├── SendDeviceCommand.php # Envoi commande vers module IoT
│ │ └── ProcessIncomingSms.php # Traitement SMS entrant
│ ├── DTO/
│ │ ├── TelemetryPayload.php # DTO ingestion telemetrie
│ │ ├── JarvisRequest.php # DTO requete Jarvis
│ │ └── DeviceCommandPayload.php # DTO commande device
│ └── Validator/
│ └── ToolCallValidator.php # Validation des function calls IA
│
└── Infrastructure/ # Adaptateurs — depend de Domain + libs externes
├── Persistence/
│ ├── DoctrineTelemetryRepository.php
│ ├── DoctrineDeviceRepository.php
│ └── DoctrineTripRepository.php
├── Messaging/
│ ├── MercureRealtimePublisher.php
│ └── SqsTelemetryConsumer.php
├── Ai/
│ └── ClaudeAgentAdapter.php # Client Claude API + function calling
├── Sms/
│ └── ModemSmsGateway.php # Envoi/reception SMS via modem 4G
└── Http/
├── Controller/
│ ├── TelemetryController.php
│ ├── JarvisController.php
│ ├── CommandController.php
│ └── SmsWebhookController.php
└── Security/
├── DeviceTokenAuthenticator.php
└── TenantFilter.php # Doctrine Filter multi-tenant
Regle de dependance : Les fleches de dependance vont toujours vers l'interieur. Infrastructure depend de Application et Domain. Application depend uniquement de Domain. Domain ne depend de rien d'externe. Cette regle est verifiee par PHPStan + Deptrac.
Documentation interactive de l'API generee par NelmioApiDocBundle.
Acces : Swagger UI est disponible a http://localhost:8080/api/doc. Le schema OpenAPI 3.0 JSON est a /api/doc.json.
config/packages/nelmio_api_doc.yaml#[OA\Tag], #[OA\Response], #[OA\RequestBody], #[OA\Parameter] sur chaque methode de controllerTelemetry, Jarvis, Device, Auth)#[OA\Property]Gestion du schema de base de donnees via les migrations Doctrine.
| Commande | Description |
|---|---|
php bin/console doctrine:migrations:diff | Genere une migration a partir des differences entre les entites et le schema actuel |
php bin/console doctrine:migrations:migrate | Execute toutes les migrations en attente |
php bin/console doctrine:migrations:status | Affiche l'etat des migrations (executees, en attente) |
php bin/console doctrine:schema:validate | Verifie la coherence entre les entites et le schema SQL |
php bin/console doctrine:database:create | Cree la base de donnees si elle n'existe pas |
php bin/console doctrine:fixtures:load | Charge les fixtures (donnees de test) — attention : ecrase les donnees existantes |
Partitionnement : La table telemetry est partitionnee par mois. Les migrations Doctrine ne gerent pas nativement le partitionnement PostgreSQL — les migrations de partitionnement sont ecrites en SQL brut dans des fichiers de migration custom.
Strategie de tests et configuration de l'environnement de test.
| Type | Repertoire | Cible |
|---|---|---|
| Unit tests | tests/Unit/ | Entites Domain, value objects, use cases Application (mocks des ports) |
| Integration tests | tests/Integration/ | Repositories Doctrine, Mercure publisher, Claude adapter (avec mocks HTTP) |
| Functional tests | tests/Functional/ | Controllers API (WebTestCase) — test du flux complet requete/reponse |
| Commande | Description |
|---|---|
php bin/phpunit | Lance tous les tests |
php bin/phpunit --filter TelemetryTest | Lance un test specifique |
php bin/phpunit --testsuite unit | Lance uniquement les tests unitaires |
php bin/phpunit --coverage-html var/coverage | Genere un rapport de couverture HTML |
Base de test : Les tests d'integration et fonctionnels utilisent une base PostgreSQL dediee (app_test) configuree dans .env.test. Les fixtures sont chargees automatiquement avant chaque suite de tests.
Commandes Symfony et Doctrine frequemment utilisees en developpement.
| Commande | Description |
|---|---|
php bin/console cache:clear | Vide le cache Symfony (obligatoire apres changement de config) |
php bin/console debug:router | Liste toutes les routes enregistrees avec methodes et patterns |
php bin/console debug:container | Liste tous les services du container avec leurs classes |
php bin/console debug:event-dispatcher | Liste les event listeners enregistres |
php bin/console messenger:consume async | Demarre le worker Messenger pour consommer la queue SQS |
php bin/console lexik:jwt:generate-keypair | Genere les cles RSA pour LexikJWTAuthenticationBundle |
php bin/console make:entity | Genere une nouvelle entite Doctrine (interactif) |
php bin/console make:controller | Genere un nouveau controller |
composer phpstan | Analyse statique PHPStan (niveau 8) |
composer cs-fix | Correction automatique du coding standard (PHP-CS-Fixer) |
Guide pas-a-pas pour ajouter une fonctionnalite en suivant l'architecture hexagonale.
Creer l'entite metier dans src/Domain/Entity/. Si besoin, ajouter des value objects dans src/Domain/ValueObject/. L'entite ne doit dependre d'aucune librairie externe (pas de Doctrine annotations ici, uniquement des attributs PHP purs).
Definir l'interface du repository ou du service externe dans src/Domain/Port/. Ex: AlertRepositoryInterface.php avec les methodes save(), findByDevice(), etc.
Creer le use case dans src/Application/UseCase/. Il recoit les ports en injection de dependance (constructor). Il orchestre la logique metier sans connaitre les implementations concretes. Creer le DTO dans src/Application/DTO/.
Implementer le port dans src/Infrastructure/. Ex: DoctrineAlertRepository.php qui implemente AlertRepositoryInterface avec Doctrine ORM. Ajouter le mapping Doctrine (XML ou attributs).
Creer le controller HTTP dans src/Infrastructure/Http/Controller/. Il recoit le use case en injection, valide le DTO, appelle le use case et retourne la reponse JSON. Ajouter les annotations OpenAPI pour Swagger.
Ecrire les tests unitaires du use case (mock des ports), les tests d'integration du repository (base PostgreSQL de test) et les tests fonctionnels du controller (WebTestCase). Verifier la couverture avec PHPUnit.