Skip to content

Architecture Globale

Vue d'ensemble

Roomee est construit sur une architecture microservices moderne dans un monorepo, orchestrée par Turborepo et gérée avec PNPM. Cette architecture permet une scalabilité, une maintenabilité et un déploiement indépendant de chaque service.

Principes Architecturaux

1. Monorepo Multi-Services

roomee-services/
├── packages/
│   └── shared/              # @roomee/shared - Code partagé
│       ├── logger/
│       ├── amqp/
│       ├── socket/
│       ├── auth/
│       └── encryption/
├── services/
│   ├── api-authentication/  # 🔐 Auth & Users
│   ├── api-hotel/          # 🏨 Ecosystems
│   ├── api-staff-member/   # 👥 Members & RBAC
│   ├── api-media/          # 📁 Storage & Media
│   ├── api-news/           # 📰 CMS
│   └── api-notification/   # 🔔 Notifications
└── turbo.json

2. Architecture Event-Driven

mermaid
graph TB
    subgraph "Frontend"
        WEB[Web App<br/>React]
        MOBILE[Mobile App<br/>React Native]
    end

    subgraph "API Layer"
        AUTH[api-authentication]
        HOTEL[api-hotel]
        MEMBER[api-staff-member]
        NEWS[api-news]
        MEDIA[api-media]
        NOTIF[api-notification]
    end

    subgraph "Infrastructure"
        AMQP[RabbitMQ<br/>Event Bus]
        MONGO[(MongoDB<br/>Per Service)]
        GCP[GCP Storage]
    end

    WEB -.REST + WebSocket.-> AUTH
    WEB -.REST + WebSocket.-> HOTEL
    WEB -.REST + WebSocket.-> MEMBER
    WEB -.REST + WebSocket.-> NEWS
    MOBILE -.REST + WebSocket.-> AUTH
    MOBILE -.REST + WebSocket.-> MEMBER

    AUTH --> AMQP
    HOTEL --> AMQP
    MEMBER --> AMQP
    NEWS --> AMQP
    MEDIA --> AMQP
    NOTIF -.Consumer.-> AMQP

    AUTH --> MONGO
    HOTEL --> MONGO
    MEMBER --> MONGO
    NEWS --> MONGO
    NOTIF --> MONGO
    MEDIA --> GCP

Types de Communication

Communication Synchrone (REST)

Usage : API publiques, opérations CRUD

Service SourceService CibleEndpointUsage
api-hotelapi-staff-memberPOST /members/add/hotel/memberCréer administrateur hôtel
api-hotelapi-stripePOST /sub/assign-subscription/Créer abonnement
api-authenticationapi-staff-memberGET /members/get/with/:emailRécupérer profil membre
api-newsapi-staff-memberPOST /members/syncSynchroniser membres

Avantages :

  • Réponse immédiate
  • Simplicité d'implémentation
  • Debugging facile

Inconvénients :

  • Couplage fort
  • Disponibilité requise
  • Latence potentielle

Communication Asynchrone (AMQP)

Usage : Événements métier, notifications, synchronisation

Routing Keys :

roomee.notification.{module}.{action}

Exemples :
- roomee.notification.page.created
- roomee.notification.member.registered
- roomee.notification.workspace.member_joined
- roomee.notification.comment.created

Pattern Pub/Sub :

typescript
// Émetteur (api-news)
await amqpServer.emit('roomee.notification.page.created', {
  type: 'page.created',
  workspaceId: '...',
  payload: pageData,
  channels: ['push', 'socket'],
  metadata: {
    actor: { id, name },
    receiverIds: [...]
  }
})

// Consommateur (api-notification)
amqpServer.subscribe('roomee.notification.page.*', async (message) => {
  await notificationProcessor.process(message)
})

Avantages :

  • Découplage total
  • Scalabilité
  • Résilience (retry, DLQ)

Inconvénients :

  • Complexité accrue
  • Éventuelle consistance
  • Debugging plus complexe

Communication Temps Réel (Socket.IO)

Usage : Mises à jour UI temps réel

Rooms et Événements :

typescript
// Connexion avec auth JWT
socket.on('connect', () => {
  socket.emit('joinWorkspace', workspaceId)
  socket.emit('subscribeToTypes', {
    workspaceId,
    types: ['page', 'comment', 'notification']
  })
})

// Réception événements
socket.on('page.created', (data) => { ... })
socket.on('notification:new', (data) => { ... })
socket.on('member.registered', (data) => { ... })

Rooms disponibles :

  • member:{memberId} - Personnel
  • workspace:{workspaceId} - Workspace
  • ecosystem:{ecosystemId} - Écosystème
  • workspace:{workspaceId}:type:{type} - Par type
  • page:{pageId} - Page spécifique

Isolation Multi-Tenant

Niveaux d'isolation

Plateforme Roomee
    └── Écosystème (hotelId / ecosystemId)
        └── Workspace (workspaceId)
            └── Membres (memberId)

Filtrage des données

Toutes les requêtes doivent filtrer par :

  1. workspaceId (isolation workspace)
  2. ecosystemId (isolation écosystème)
  3. deleted_at IS NULL (soft delete)
typescript
// ✅ BON
const pages = await prisma.page.findMany({
  where: {
    workspaceId: req.user.workspaceId,
    deleted_at: null
  }
})

// ❌ MAUVAIS (fuite multi-tenant)
const pages = await prisma.page.findMany({
  where: { deleted_at: null }
})

Permissions RBAC

Système à 2 niveaux :

  1. Permissions Écosystème (generales)

    • access_live_chat
    • screenshot_permission
    • manage_ecosystem
  2. Permissions Workspace (role-based)

    • Définies par workspaceRole
    • Stockées dans permissionSlugs[]
    • Catégorisées : members, content, settings, communication

Gestion des Erreurs

Hiérarchie des erreurs (@roomee/shared)

typescript
AppError (base)
├── ValidationError (400)
├── UnauthorizedError (401)
├── ForbiddenError (403)
├── NotFoundError (404)
├── ConflictError (409)
├── TooManyRequestsError (429)
├── InternalServerError (500)
├── ServiceUnavailableError (503)
├── DatabaseError (500)
└── ExternalServiceError (502)

Gestion centralisée

typescript
// Middleware error handler
app.use(errorConverter)
app.use(errorHandler)

// Capture Sentry
sentryService.captureException(error)

// Logs Winston
logger.error('Error processing request', { error, context })

Monitoring et Observabilité

Logging (Winston)

Niveaux : error, warn, info, debug

typescript
logger.info('User logged in', { userId, email })
logger.error('Database connection failed', { error })

Transports :

  • Console (dev)
  • Daily rotate file (prod)
  • MongoDB (analytics)
  • Discord webhook (alertes)

Error Tracking (Sentry)

typescript
sentryService.initialize({
  dsn: process.env.SENTRY_DSN,
  environment: 'production',
  tracesSampleRate: 1.0
})

Health Checks

http
GET /health

{
  "status": "UP",
  "service": "api-authentication",
  "timestamp": "2025-10-31T12:00:00.000Z"
}

Sécurité

Authentification JWT

Token chiffré :

typescript
// Format : {encryptedText}.{iv}.{tag}
const token = authMiddleware.generateToken({
  userId,
  email,
  workspaceId,
  roles,
  permissions
})

Validation :

typescript
// Middleware @roomee/shared
router.use(authMiddleware.middleware)

Protection des API

typescript
// Helmet (headers sécurité)
app.use(helmet())

// CORS
app.use(cors({ origin: allowedOrigins }))

// NoSQL Injection
app.use(mongoSanitize())

// Rate Limiting
app.use('/auth', rateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 20
}))

Validation des entrées

Zod schemas (services modernes) :

typescript
const createPageSchema = z.object({
  title: z.string().min(1).max(255),
  content: z.any(),
  workspaceId: z.string().regex(/^[0-9a-fA-F]{24}$/)
})

router.post('/', validate(createPageSchema), controller)

Scalabilité

Stratégies

  1. Horizontal Scaling

    • Chaque service peut être répliqué
    • Load balancing via Nginx/Traefik
  2. Database Sharding

    • Une base MongoDB par service
    • Possibilité de sharding par workspaceId
  3. AMQP Queue Workers

    • Plusieurs consumers par queue
    • Prefetch pour contrôler la charge
  4. Caching (à implémenter)

    • Redis pour les permissions
    • TTL 5 minutes

Déploiement

Environnements

EnvInfraBranchURL
localDocker Compose-localhost
devK8s Devdevelopdev.roomee.io
stagingK8s Stagingstagingstaging.roomee.io
prodK8s Prodmainapi.roomee.io

CI/CD Pipeline

yaml
# .github/workflows/deploy.yml
name: Deploy Service

on:
  push:
    paths:
      - 'services/api-authentication/**'
    branches: [main, staging, develop]

jobs:
  deploy:
    - Install dependencies (pnpm)
    - Build @roomee/shared
    - Build service
    - Run tests
    - Build Docker image
    - Push to registry
    - Deploy to K8s

Configuration Secrets (Infisical)

Hiérarchie :

COMMON/          # Variables partagées (DB, JWT, API_BASE_URL)
AMQP/            # Configuration RabbitMQ
Customer/        # Variables spécifiques au service
Chat/            # Sendbird, etc.
typescript
// Chargement automatique
const config = await configService.configureEnvVar()

Bonnes Pratiques

Repository Pattern

typescript
// Controller → Service → Repository → Prisma → DB
const member = await memberRepository.findById(id)

Soft Delete

typescript
// Toujours soft delete
await prisma.page.update({
  where: { id },
  data: { deleted_at: new Date(), deletedById }
})

// Toujours filtrer
where: { deleted_at: null }

Pagination

typescript
const { page = 1, limit = 20 } = req.query
const skip = (page - 1) * limit

const [items, total] = await Promise.all([
  prisma.page.findMany({ skip, take: limit }),
  prisma.page.count()
])

return {
  data: items,
  meta: {
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit)
  }
}

Transactions

typescript
await prisma.$transaction(async (tx) => {
  const page = await tx.page.create({ data })
  await tx.notification.create({ data: { pageId: page.id } })
})

Évolutions Futures

Court Terme

  • [ ] API Gateway (Kong/Traefik)
  • [ ] Cache Redis global
  • [ ] GraphQL Federation
  • [ ] Swagger UI centralisé

Moyen Terme

  • [ ] Service Mesh (Istio)
  • [ ] Distributed Tracing (Jaeger)
  • [ ] Feature Flags (LaunchDarkly)

Long Terme

  • [ ] Event Sourcing
  • [ ] CQRS Pattern
  • [ ] Kubernetes Federation multi-cloud

Documentation technique Roomee Services