docs: add 05-temps-et-base-de-donnees guide and update architecture documentation
This commit is contained in:
parent
5c49b2cc4f
commit
4a3d0c46a8
3 changed files with 175 additions and 3 deletions
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
167
documentation/Architecture/05-temps-et-base-de-donnees.md
Normal file
167
documentation/Architecture/05-temps-et-base-de-donnees.md
Normal 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.
|
||||
Loading…
Add table
Reference in a new issue