Architecture Globale

Vue d’ensemble

Updo Backend suit les principes du Domain-Driven Design (DDD) avec une architecture en Bounded Contexts indépendants. Chaque contexte encapsule ses propres modèles, services, événements et vues API.

Structure des répertoires

updo_backend/
├── updo/                          # Configuration Django
│   ├── settings.py                # Paramètres du projet
│   ├── urls.py                    # Routes URL racine
│   ├── celery.py                  # Configuration Celery
│   └── exception_handler.py       # Gestionnaire d'exceptions DRF
├── src/
│   ├── share_kernel/              # Shared Kernel (code partagé)
│   │   ├── domain/                # Entités, VO, events, exceptions
│   │   ├── application/           # CQRS (Command, Query, UseCase)
│   │   └── infrastructure/        # Django models, repos, isolation
│   │       └── isolation/         # Backends d'isolation (schema, RLS, FK)
│   ├── module/
│   │   ├── tenant/                # Tenant Context
│   │   │   ├── domain/            # Events, services, VO
│   │   │   ├── middleware.py       # Résolution du tenant
│   │   │   ├── models.py          # Tenant, Membership, Invitation
│   │   │   └── management/        # Commandes de gestion
│   │   ├── identity/              # Identity Context
│   │   │   ├── domain/            # Events, OTP service
│   │   │   ├── models.py          # User, OTPDevice, EmailVerification
│   │   │   └── authentication.py  # JWT personnalisé
│   │   └── authorization/         # Authorization Context
│   │       ├── domain/            # ABAC evaluation (PDP)
│   │       ├── condition_engines/ # JSON, Cedar, Casbin
│   │       ├── permissions.py     # PEP (DRF permissions)
│   │       ├── pip.py             # PIP (résolution d'attributs)
│   │       └── models.py          # Policy, ApprovalRequest
│   └── testing/                   # Utilitaires de test
└── docs/                          # Documentation Sphinx

Les 3 Bounded Contexts

Tenant Context

Gère les organisations (tenants), les memberships utilisateur-tenant, les rôles, départements et invitations. Fournit le middleware de résolution du tenant et les backends d’isolation de données.

Responsabilités :

  • Cycle de vie du tenant (création, suspension, archivage)

  • Gestion des memberships et rôles

  • Isolation des données (schema, RLS, FK partagée)

  • Provisionnement et migration des schémas

  • Système d’invitations par email

Identity Context

Gère l’authentification, les utilisateurs et la sécurité multi-facteurs.

Responsabilités :

  • Modèle utilisateur personnalisé (email comme identifiant)

  • Authentification JWT avec claims tenant

  • OTP/2FA (Email, TOTP RFC 6238, codes statiques)

  • Vérification d’email

  • Changement de tenant (switch tenant)

Authorization Context

Implémente le contrôle d’accès basé sur les attributs (ABAC) et les workflows d’approbation.

Responsabilités :

  • Moteur ABAC complet (PDP, PEP, PIP, PAP)

  • Moteurs de conditions pluggables (JSON, Cedar, Casbin)

  • Workflows d’approbation avec quorum

  • Politiques statiques système

  • Décorateurs @abac_required

Flux d’une requête HTTP

Client HTTP
    │
    ▼
Django Middleware Stack
    │
    ├── SecurityMiddleware
    ├── SessionMiddleware
    ├── CommonMiddleware
    ├── CsrfViewMiddleware
    ├── AuthenticationMiddleware (JWT → User)
    ├── TenantMiddleware          ◄── Résolution du tenant
    │   ├── Extrait tenant_id du JWT ou header X-Tenant-ID
    │   ├── Valide que le tenant est actif
    │   ├── Vérifie la membership de l'utilisateur
    │   ├── Active le backend d'isolation
    │   └── Set contextvars (tenant, user, membership)
    │
    ▼
DRF View
    ├── ABACPermission.has_permission()  ◄── Évaluation ABAC
    │   ├── PIP résout les attributs
    │   ├── PDP évalue les politiques
    │   └── Décision: allow / deny / approval_required
    │
    ▼
Réponse HTTP

Bus d’événements domaine

Les événements domaine permettent le découplage entre bounded contexts. Le bus supporte deux modes :

  • Synchrone : handlers exécutés immédiatement dans la même transaction

  • Asynchrone : handlers dispatchés via transaction.on_commit() + Celery

from src.share_kernel.infrastructure.event_bus import publish_event
from src.module.tenant.domain.events import TenantCreated

# L'événement est publié après le commit de la transaction
publish_event(TenantCreated(
    tenant_id=str(tenant.id),
    tenant_slug=tenant.slug,
    schema_name=tenant.schema_name,
))