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