Arquitetura — Rune Courier
Visao Geral
graph TB
subgraph Frontends
FE1[Hotel Frontend<br/>:5173]
FE2[Chronicle Frontend<br/>:3040]
FE3[Chronos Frontend<br/>:3050]
end
subgraph "Rune Courier"
REST[REST API<br/>/bff/*]
WS[WebSocket<br/>/ws/chat]
end
subgraph Dados
PG[(PostgreSQL<br/>:5460)]
REDIS[(Redis<br/>:6381)]
end
subgraph Servicos Haus
OATH[OATH<br/>:5001]
GUILD[Guild<br/>:5002]
HERALD[Herald<br/>:5004]
SCROLLS[Scrolls<br/>:5003]
end
FE1 --> REST
FE1 <--> WS
FE2 --> REST
FE2 <--> WS
FE3 --> REST
FE3 <--> WS
REST --> PG
REST --> REDIS
REST --> OATH
REST --> GUILD
WS --> REDIS
REST --> HERALD
REST --> SCROLLS
Backend
Camadas
parley/
├── api/
│ ├── controllers/ # REST controllers (/bff/*)
│ │ ├── BffConversationController.java
│ │ ├── BffMessageController.java
│ │ ├── BffUserController.java
│ │ └── HealthController.java
│ ├── dtos/ # Request/Response DTOs
│ ├── websocket/ # WebSocket (/ws/chat)
│ │ ├── ChatWebSocket.java
│ │ ├── ChatEvent.java
│ │ └── ChatEventType.java
│ └── exceptions/ # GlobalExceptionHandler
├── application/services/
│ ├── ConversationService.java
│ ├── MessageService.java
│ ├── PresenceService.java
│ └── ReadReceiptService.java
├── domain/
│ ├── entities/ # Panache entities
│ │ ├── Conversation.java
│ │ ├── ConversationParticipant.java
│ │ └── Message.java
│ └── enums/
│ ├── ConversationType.java # DIRECT, GROUP
│ ├── ContentType.java # TEXT, IMAGE, FILE, SYSTEM
│ └── ParticipantRole.java # ADMIN, MEMBER
└── infrastructure/
├── cache/RedisCacheService.java
├── security/
│ ├── AuthFilter.java # extends BaseAuthFilter
│ └── ParleyContext.java # (applicationId, tenantId, userId)
├── startup/GuildInitializer.java
└── websocket/ChatBroadcastService.java
Seguranca
sequenceDiagram
participant FE as Frontend
participant AF as AuthFilter
participant OATH as OATH Service
participant PC as ParleyContext
participant C as Controller
FE->>AF: Request + Bearer Token<br/>+ X-Application-Id<br/>+ X-Tenant-Id
AF->>OATH: POST /auth/introspect
OATH-->>AF: User context (id, email, roles)
AF->>PC: Popula (appId, tenantId, userId)
PC->>PC: requireContext() valida
AF->>C: Request processada
Modelo de Dados
erDiagram
Conversation ||--o{ ConversationParticipant : "tem"
Conversation ||--o{ Message : "contem"
Message }o--o| Message : "reply_to"
Conversation {
uuid id PK
varchar application_id
varchar tenant_id
varchar type "DIRECT | GROUP"
varchar name
uuid created_by
boolean is_active
jsonb metadata
timestamp created_at
}
ConversationParticipant {
uuid id PK
uuid conversation_id FK
uuid user_id
varchar user_email
varchar user_name
varchar role "ADMIN | MEMBER"
boolean is_active
uuid last_read_message_id
timestamp joined_at
}
Message {
uuid id PK
uuid conversation_id FK
uuid sender_id
varchar sender_email
varchar sender_name
text content
varchar content_type "TEXT | IMAGE | FILE | SYSTEM"
uuid reply_to_id FK
boolean is_edited
boolean is_deleted
jsonb metadata
timestamp created_at
}
Indices
| Tabela |
Indice |
Colunas |
| conversations |
idx_conversations_tenant |
(application_id, tenant_id) |
| conversations |
idx_conversations_active |
(is_active) |
| conversation_participants |
UNIQUE |
(conversation_id, user_id) |
| conversation_participants |
idx_participants_user |
(user_id, is_active) |
| messages |
idx_messages_conversation |
(conversation_id, created_at DESC) |
Migrations (Flyway)
| Versao |
Descricao |
V1 |
CREATE TABLE conversations + indices |
V2 |
CREATE TABLE conversation_participants + unique constraint |
V3 |
CREATE TABLE messages + partial index (non-deleted) |
WebSocket — Broadcast
O ChatBroadcastService gerencia conexoes ativas:
ConcurrentHashMap<String, Map<UUID, Map<String, WebSocketConnection>>>
│ │ │
│ │ └── connectionId → connection
│ └── userId → connections
└── tenantKey (appId:tenantId) → users
Fluxo de mensagem
sequenceDiagram
participant U1 as User A
participant REST as REST API
participant DB as PostgreSQL
participant BC as BroadcastService
participant U2 as User B (WebSocket)
U1->>REST: POST /bff/conversations/{id}/messages
REST->>DB: INSERT message
REST->>DB: UPDATE conversation.updated_at
REST->>BC: broadcastNewMessage(conversationId, message)
BC->>BC: Busca participantes da conversa
BC->>BC: Filtra conexoes ativas no tenant
BC->>U2: WebSocket: MESSAGE_RECEIVED
Autenticacao WebSocket
sequenceDiagram
participant FE as Frontend
participant WS as ChatWebSocket
participant OATH as OATH Service
participant BC as BroadcastService
FE->>WS: Connect ws://host/ws/chat<br/>?token={jwt}&app={appId}&tenant={tenantId}
WS->>OATH: POST /auth/introspect (token)
OATH-->>WS: User context
WS->>BC: registerConnection(appId, tenantId, userId, connection)
WS-->>FE: Connected
Note over FE,WS: Ping/pong para manter conexao
FE->>WS: {"type":"TYPING","conversationId":"uuid"}
WS->>BC: broadcastTyping(conversationId, userId, TYPING_START)
Redis
Estrutura de chaves
| Chave |
Tipo |
TTL |
Uso |
parley:presence:{app}:{tenant} |
SET |
- |
User IDs online |
parley:typing:{conversationId}:{userId} |
STRING |
5s |
Indicador de digitacao |
Operacoes
| Operacao |
Comando Redis |
| Marcar online |
SADD parley:presence:{key} {userId} |
| Marcar offline |
SREM parley:presence:{key} {userId} |
| Listar online |
SMEMBERS parley:presence:{key} |
| Verificar online |
SISMEMBER parley:presence:{key} {userId} |
| Indicar typing |
SET parley:typing:{convId}:{userId} 1 EX 5 |
Guild — Roles e Permissoes
O GuildInitializer registra no startup:
Roles
| Role |
Descricao |
PARLEY_ADMIN |
Administracao do chat (gerenciar conversas e participantes) |
PARLEY_USER |
Usuario padrao (criar conversas e enviar mensagens) |
Permissoes
| Permissao |
Descricao |
parley:conversation:create |
Criar conversas |
parley:conversation:read |
Ler conversas |
parley:conversation:manage |
Gerenciar conversas (editar, adicionar participantes) |
parley:message:send |
Enviar mensagens |
parley:message:edit |
Editar proprias mensagens |
parley:message:delete |
Deletar proprias mensagens |
parley:admin:* |
Acesso administrativo total |
Configuracao
Portas
| Servico |
Porta externa |
Porta interna |
| Parley app |
5060 |
8080 |
| PostgreSQL |
5460 |
5432 |
| PgBouncer |
6460 |
6432 |
| Redis |
6381 |
6379 |
Variaveis de Ambiente
# Banco
DB_USER=parley
DB_PASSWORD=parley123
DB_URL=jdbc:postgresql://pgbouncer-parley:6432/parleydb
# Redis
REDIS_HOST=redis://parley-redis:6379
# Servicos
OATH_API_URL=http://oath:8080
GUILD_API_URL=http://guild:8080
HERALD_API_URL=http://herald:8080
SCROLLS_URL=http://scrolls:8080
# Aplicacao
GUILD_APPLICATION_ID=parley
GUILD_AUTH_EMAIL=parley
GUILD_AUTH_PASSWORD=PARLEY987!@#
Observabilidade
- Logs: JSON estruturado para Grafana/Loki (
service=parley)
- Metricas: Prometheus via Micrometer (
/q/metrics)
- Health:
/health, /health/ready, /health/live
- Audit: Scrolls para eventos criticos (criacao conversa, delete mensagem)