docs: add 05-temps-et-base-de-donnees guide and update architecture documentation

This commit is contained in:
Jérémy Munsch 2026-08-21 16:13:39 +02:00
parent 5c49b2cc4f
commit 4a3d0c46a8
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
3 changed files with 175 additions and 3 deletions

View file

@ -13,6 +13,7 @@ L'ensemble de la documentation technique et des guides pratiques est organisé d
- **[02 - Cycle de Vie d'une Requête (Bullet Tracer)](documentation/Architecture/02-cycle-de-vie-requete.md)** : Traçage pas-à-pas d'une requête HTTP depuis la route jusqu'à la sérialisation JSON.
- **[03 - Exécution, Transactions & Événements](documentation/Architecture/03-execution-et-transactions.md)** : `UsecaseBuilder`, `LaravelExecutor`, exécution transactionnelle et dispatch sécurisé post-commit (`TransactionalDomainEventDispatcher`).
- **[04 - Guide Nouveau Cas d'Usage](documentation/Architecture/04-guide-nouveau-usecase.md)** : Checklist pas-à-pas pour créer une nouvelle fonctionnalité de bout en bout.
- **[05 - Temps, Horloge PSR-20 & Base de Données](documentation/Architecture/05-temps-et-base-de-donnees.md)** : Contrat PSR-20 `Clock`, Façade `Utc`, tests avec `FrozenClock`, standardisation `DATETIME` et dérivation `created_at` depuis UUIDv7.
### 📐 Conventions & Standards
- **[Conventions de Tests](documentation/Conventions/tests.md)** : Principes F.I.R.S.T., structure GWT, pyramide de tests, **Test Data Builders** et **Object Mothers**.

View file

@ -72,15 +72,19 @@ Arcadia API combine 3 paradigmes éprouvés pour créer une base de code hauteme
- **Contenu** :
- `Adapters/` : Implémentations Eloquent des Ports (`EloquentEmployeeDirectory`, `EloquentBrandRepository`).
- `Database/Models/` : Modèles Eloquent anémiques (mapping BDD uniquement).
- `Database/Concerns/` : Traits de persistance (`HasUuidV7CreatedAt`).
- `Casts/` : Casts Eloquent bidirectionnels (`UtcDateTimeCast`, `UuidCast`, etc.).
- `Http/Middleware/` : Middlewares techniques (HMAC, Auth).
- `Providers/` : Configuration des bindings Laravel (`DomainServiceProvider`, `SecurityServiceProvider`).
- `Providers/` : Configuration des bindings Laravel (`DomainServiceProvider`, `SecurityServiceProvider`, `ConfigServiceProvider`).
- `Usecase/LaravelExecutor.php` : Exécuteur transactionnel et logging PSR-3.
---
### 5. `Shared\` - Le Socle DDD Transverse
- **Dépendance** : Zéro dépendance externe.
- **Contenu** : Interfaces de base (`Entity`, `EntityId`, `Usecase`, `Input`, `Output`, `Presenter`, `DomainEventDispatcher`, `TransactionalDomainEventDispatcher`, `UsecaseBuilder`, `Paginated`, `Pagination`).
- **Dépendance** : Zéro dépendance externe (hors PSR-20).
- **Contenu** :
- Socle DDD (`Entity`, `EntityId`, `Usecase`, `Input`, `Output`, `Presenter`, `DomainEventDispatcher`, `TransactionalDomainEventDispatcher`, `UsecaseBuilder`, `Paginated`, `Pagination`).
- Gestion du temps (`Shared\Time\` : `Clock` PSR-20, `SystemClock`, `SystemUtcClock`, `FrozenClock`, Façade `Utc`).
---

View file

@ -0,0 +1,167 @@
# Gestion du Temps, Horloge PSR-20 & Persistance des Dates - Arcadia API
Ce document détaille la stratégie de gestion temporelle, le contrat d'horloge PSR-20 (`ClockInterface`), la normalisation **UTC / RFC 3339** et la politique de stockage des dates en base de données dans **Arcadia API**.
---
## 📑 Sommaire
- [1. Principes Fondamentaux](#1-principes-fondamentaux)
- [2. L'Horloge PSR-20 & La Façade `Utc`](#2-lhorloge-psr-20--la-façade-utc)
- [3. Tests & Voyage Temporel (`FrozenClock`)](#3-tests--voyage-temporel-frozenclock)
- [4. Normalisation des Sorties d'API (RFC 3339)](#4-normalisation-des-sorties-dapi-rfc-3339)
- [5. Persistance en Base de Données (`DATETIME` vs `TIMESTAMP`)](#5-persistance-en-base-de-données-datetime-vs-timestamp)
- [6. Dérivation Automatique de `created_at` depuis UUIDv7](#6-dérivation-automatique-de-created_at-depuis-uuidv7)
---
## 1. Principes Fondamentaux
1. **UTC partout en interne** : Tout calcul métier, comparaison et stockage temporel s'effectue strictement en UTC (`\DateTimeImmutable` avec `DateTimeZone('UTC')`).
2. **Immutabilité stricte** : `\DateTime` (mutable) est **strictement interdit** dans tout le code source (contrôlé par les tests d'architecture Pest). Seul `\DateTimeImmutable` est autorisé.
3. **Contrat d'Horloge Injectable (PSR-20)** : Tout composant ayant besoin de l'instant présent doit consommer le contrat `Shared\Time\Clock` (ou la façade `Utc`) pour éliminer les appels directs non déterministes à `now()`, `time()` ou `new DateTimeImmutable()`.
4. **Pas de bug de l'an 2038** : Le type SQL `DATETIME` est la norme absolue sur toutes les tables.
---
## 2. L'Horloge PSR-20 & La Façade `Utc`
### A. Le Contrat `Shared\Time\Clock`
Arcadia API implémente le standard officiel **PSR-20 (Clock)** via l'interface `Shared\Time\Clock` :
```php
namespace Shared\Time;
use Psr\Clock\ClockInterface;
interface Clock extends ClockInterface
{
#[\Override]
public function now(): \DateTimeImmutable;
public function timezone(): \DateTimeZone;
public function utcNow(): \DateTimeImmutable;
}
```
- **`SystemClock`** : Horloge système initialisée dynamiquement avec le fuseau configuré (`$appConfig->timezone->toDateTimeZone()`).
- **`SystemUtcClock`** : Horloge système garantie 100% UTC.
### B. La Façade Statique `Utc`
Pour les besoins transverses et les formattages sans injection lourde, la façade statique `Shared\Time\Utc` fournit des helpers sécurisés :
```php
use Shared\Time\Utc;
// Obtenir l'instant présent en UTC
$now = Utc::now();
// Formater en RFC 3339 (ex: 2026-08-21T14:30:00Z)
$formatted = Utc::toRfc3339($dateTime);
// Parser une date brute et garantir son fuseau UTC
$parsed = Utc::parse('2026-08-21 14:30:00');
```
---
## 3. Tests & Voyage Temporel (`FrozenClock`)
Dans les tests unitaires et d'intégration, il est interdit de dépendre de l'horloge réelle de la machine hôte. Utilisez `FrozenClock` ou `Utc::freeze()` :
```php
use Shared\Time\FrozenClock;
use Shared\Time\Utc;
it('valide l expiration du token avec precision', function (): void {
// GIVEN - Figer le temps
$fixedDate = new \DateTimeImmutable('2026-08-21 12:00:00 UTC');
$clock = new FrozenClock($fixedDate);
// OU via la façade Utc
Utc::freeze($fixedDate);
// WHEN
$validator = new TimestampValidatorImpl($clock);
$isValid = $validator->validate('2026-08-21T12:02:00Z');
// THEN
expect($isValid)->toBeTrue();
// Nettoyage
Utc::reset();
});
```
---
## 4. Normalisation des Sorties d'API (RFC 3339)
Toutes les dates exposées par l'API (dans les DTOs sous `Presentation\Api\V1\Dto\`) doivent être sérialisées au format **RFC 3339** (avec le suffixe `Z` pour UTC) :
```php
final readonly class BrandDto implements ApiResponseDto
{
public function __construct(private BrandView $view) {}
public function toArray(): array
{
return [
'id' => $this->view->id,
'name' => $this->view->name,
'created_at' => Utc::toRfc3339($this->view->createdAt),
];
}
}
```
---
## 5. Persistance en Base de Données (`DATETIME` vs `TIMESTAMP`)
### Pourquoi `DATETIME` sur 100% des Tables ?
1. **Pérennité (Pas de bug 2038)** : `TIMESTAMP` est stocké sur un entier 32-bit signé qui déborde le **19 janvier 2038**. `DATETIME` est valide jusqu'à l'an 9999.
2. **Déterminisme & Absence de Magie SQL** : `TIMESTAMP` applique des conversions implicites en fonction de la variable `@@session.time_zone` de MySQL/MariaDB. `DATETIME` stocke la valeur brute exacte transmise par l'application.
3. **Cast Eloquent Bidirectionnel (`UtcDateTimeCast`)** :
```php
// Infra/Casts/UtcDateTimeCast.php
final class UtcDateTimeCast implements CastsAttributes
{
// Convertit toute valeur DB en DateTimeImmutable UTC à la lecture
// Formate en 'Y-m-d H:i:s' UTC à l'écriture
}
```
---
## 6. Dérivation Automatique de `created_at` depuis UUIDv7
Les clés primaires de nos agrégats (`brands`, `api_clients`, `client_audit_logs`) utilisent des **UUIDv7**.
Comme un UUIDv7 encode de manière standardisée l'instant de création dans ses 48 premiers bits :
### Le Trait `HasUuidV7CreatedAt` (`Infra\Database\Concerns\`)
Au lieu de colonnes calculées complexes dont les dialectes varient entre MySQL, MariaDB et PostgreSQL, nous utilisons le trait Eloquent **`HasUuidV7CreatedAt`** :
```php
namespace Infra\Database\Models;
use Illuminate\Database\Eloquent\Model;
use Infra\Database\Concerns\HasUuidV7CreatedAt;
final class Brand extends Model
{
use HasUuidV7CreatedAt;
// Lors du 'creating', le trait extrait automatiquement
// les millisecondes de l'UUIDv7 ($model->id) et renseigne 'created_at'
}
```
### Règle de Schéma
- Les colonnes `created_at` sont déclarées **`dateTime('created_at')` non-nullables** dans les migrations.
- L'intégrité est totale : si un modèle est inséré sans UUIDv7 valide ou sans `created_at`, la base de données rejette la requête.
- Le Domaine reste **100% pur** : l'entité de domaine dérive son `createdAt()` directement de son `BrandId` (Value Object) sans dépendance d'infrastructure.