System Architecture
Karmyq Architecture
Version: see package.json (source of truth)
Last Updated: 2026-07-29 (Sprint 122 — Express 5 baseline)
Status: Demo/Development
Table of Contents
- Overview
- System Architecture
- Microservices
- Multi-Tenancy
- Event-Driven Communication
- Database Schema
- Authentication & Authorization
- API Patterns
- Monorepo Structure
- Technology Stack
- Infrastructure
- Observability
Overview
Karmyq is a multi-tenant SaaS mutual aid platform where community members help each other through a karma-based reputation system.
Key Characteristics
- Microservices Architecture - 9 independent backend services (feed folded into request-service, Sprint 91 / ADR-071)
- Multi-Tenant SaaS - Row-Level Security (RLS) for data isolation
- Event-Driven - Asynchronous communication via Redis/Bull queues
- Ephemeral Data - Configurable TTL for requests and reputation decay
- Monorepo - Turborepo for unified development experience
Design Principles
- Service Independence - Each service can be developed, tested, and deployed independently
- Data Isolation - Community data strictly isolated at database level (RLS)
- Loose Coupling - Services communicate via REST APIs and events
- Eventual Consistency - Accept temporary inconsistency for better scalability
- Fail Gracefully - Services degrade gracefully when dependencies unavailable
System Architecture
High-Level View
┌─────────────────────────────────────────────────────────────┐
│ Client Applications │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Web App │ │ Mobile App │ │
│ │ (Next.js) │ │ (React Native)│ │
│ │ Port 3000 │ │ (Expo) │ │
│ └──────────────┘ └──────────────┘ │
└───────────────────┬──────────────────┬──────────────────────┘
│ │
│ REST APIs │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Backend Services │
│ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Auth │ │Community │ │ Request │ │Reputation│ │
│ │ :3001 │ │ :3002 │ │ :3003 │ │ :3004 │ │
│ └─────────┘ └──────────┘ └─────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ │
│ │Notifica- │ │Messaging │ │Soc-Graph│ │ Cleanup │ │
│ │ tion │ │ :3006 │ │ :3010 │ │ :3008 │ │
│ │ :3005 │ └──────────┘ └─────────┘ └──────────┘ │
│ └──────────┘ │
│ (the personalized feed is served by Request at /requests/feed) │
└───────────────┬──────────────────┬──────────────────────────┘
│ │
┌──────────▼──────────┐ │
│ Event Queue │◄─────┘
│ (Redis + Bull) │
│ karmyq-events │
└─────────────────────┘
│
┌──────────▼──────────┐
│ PostgreSQL 15 │
│ (7 schemas) │
│ Row-Level Security│
└─────────────────────┘
Service Communication Patterns
Synchronous (REST)
- Frontend → Services: API calls with JWT authentication
- Service → Service: Rare, only when immediate response needed
- Example: Frontend calls Auth service for login
Asynchronous (Events)
- Service → Event Queue: Publish events to Redis/Bull
- Event Queue → Services: Subscribers consume events
- Example: Request service publishes
match_completedevent, Reputation service consumes it
Microservices
Service Catalog
| Service | Port | Purpose | Database Schema | Status |
|---|---|---|---|---|
| auth-service | 3001 | User authentication, JWT tokens | auth | ✅ Production |
| community-service | 3002 | Community management, memberships | community | ✅ Production |
| request-service | 3003 | Help requests, offers, matching, unified feed source-of-truth (ADR-066) | requests | ✅ Production |
| reputation-service | 3004 | Karma tracking, trust scores | reputation | ✅ Production |
| notification-service | 3005 | Real-time notifications (SSE) | notifications | ✅ Production |
| messaging-service | 3006 | Direct messaging, conversations | messaging | ✅ Production |
| cleanup-service | 3008 | Data expiration, reputation decay | - (writes all) | ✅ Production |
Service Responsibilities
Auth Service (3001)
- User registration with email/password
- Login and JWT token generation
- Token verification (used by all services)
- User profile management
- No dependencies on other services
Community Service (3002)
- Create/update/delete communities
- Public vs private communities
- Membership management (roles: admin, moderator, member)
- Join requests for private communities
- Community norms/guidelines
- Dependencies: Auth (for user info)
Request Service (3003)
- Create help requests (with urgency, category)
- Create offers to help
- Match requests with offers
- Mark matches as completed
- Publishes:
match_completed,request_created - Dependencies: Community (for community validation)
Reputation Service (3004)
- Track karma points (helping, receiving help)
- Calculate trust scores (0-100)
- Award badges and bonuses
- Consumes:
match_completed(to award karma) - Publishes:
karma_awarded
Notification Service (3005)
- Store notifications in database
- Real-time delivery via Server-Sent Events (SSE)
- User notification preferences
- Mark read/unread
- Consumes: All events (creates notifications)
- SSE is authenticated via a short-lived JWT passed as a query param (EventSource can't set headers); nginx scrubs the token from access logs (Sprint 83). Token TTL 1h. SSE auth tests are in the regression tier.
Messaging Service (3006)
- Direct conversations between users
- Message threading
- Read receipts
- Dependencies: Auth, Community
Feed (served by Request Service at /requests/feed, Sprint 91 / ADR-071)
- Aggregate activity across schemas; personalized feed per user
- Folded into request-service as a
/requests/feed/*view layer (no standalone feed-service) - Owns the
feed.preferences+feed.dismissed_itemstables
Cleanup Service (3008)
- Expire old requests (configurable TTL)
- Reputation decay (6-month half-life)
- Delete old notifications
- Archive completed requests
- Runs scheduled jobs (cron)
- Writes to all schemas
Multi-Tenancy
Design
Karmyq uses database-level multi-tenancy with PostgreSQL Row-Level Security (RLS).
Key Concept: community_id
Every data table has a community_id column:
CREATE TABLE requests.help_requests (
id UUID PRIMARY KEY,
community_id UUID NOT NULL REFERENCES community.communities(id),
requester_id UUID NOT NULL,
title TEXT NOT NULL,
-- ... other fields
);
Row-Level Security (RLS)
RLS policies enforce data isolation at the database level:
-- Enable RLS on table
ALTER TABLE requests.help_requests ENABLE ROW LEVEL SECURITY;
-- Create policy
CREATE POLICY community_isolation
ON requests.help_requests
USING (community_id = current_setting('app.current_community_id')::uuid);
Middleware Chain
Every authenticated request goes through:
app.use(authMiddleware); // 1. Verify JWT, extract userId
app.use(tenantMiddleware); // 2. Extract community_id from JWT
app.use(dbContextMiddleware(pool)); // 3. Set session variable
The dbContextMiddleware sets the session variable:
await pool.query('SET LOCAL app.current_community_id = $1', [communityId]);
Now all queries automatically filter by this community!
Multi-Community Users
Users can belong to multiple communities. The JWT contains a communities array (field name is
communities, NOT communityMemberships — auth middleware reads user.communities; the wrong field
always 403s):
{
"userId": "uuid",
"email": "user@example.com",
"communities": [
{ "id": "uuid1", "name": "Community A", "role": "admin" },
{ "id": "uuid2", "name": "Community B", "role": "member" }
]
}
The frontend sends X-Community-Context header to specify which community:
X-Community-Context: uuid1
Special Services
Feed View Layer & Cleanup Service
These operate across all communities:
- Feed (request-service
/requests/feed): Read-only cross-schema queries, no RLS needed - Cleanup Service: Writes with RLS disabled (after authorization)
// Cleanup service disables RLS for admin operations
await query('BEGIN');
await query('SET LOCAL row_security = off');
// ... admin queries
await query('COMMIT');
See TR-002: Multi-Tenancy for details.
Event-Driven Communication
Event Queue
- Technology: Bull (Node.js job queue)
- Backend: Redis
- Queue Name:
karmyq-events
Sprint 132 PR C (ADR-099) isolates two deliveries from that legacy queue. Directed borrow
notifications use karmyq-directed-notifications with transactional borrow intent and live
recipient resolution. New match_completed events use karmyq-completion-dispatch, whose
request-service processor publishes independent jobs to karmyq-completion-reputation,
karmyq-completion-notification and karmyq-completion-social-graph. Bull distributes one
queue's jobs among workers; named handlers do not broadcast to every service. Stable match
job IDs allow partial fanout retries; completed identities remain for 24 hours without count
eviction. Delivery remains at least once. Legacy shared handlers stay for older jobs, whose
competition is not repaired. The existing gap before the first completion enqueue and graph
counter retry effects remain documented in ADR-099.
Event Flow
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ Publisher │────────>│ Redis Queue │───────>│ Subscriber │
│ (Service) │ publish │ (Bull) │ consume│ (Service) │
└──────────────┘ └─────────────┘ └──────────────┘
Core Events
1. match_completed
Published by: request-service Consumed by: reputation-service, notification-service, social-graph-service
{
eventType: 'match_completed',
payload: {
match_id: 'uuid',
request_id: 'uuid',
requester_id: 'uuid',
responder_id: 'uuid'
}
}
2. karma_awarded
Published by: reputation-service Consumed by: notification-service
{
type: 'karma_awarded',
payload: {
user_id: 'uuid',
community_id: 'uuid',
points: 25,
reason: 'helped_with_request',
details: { ... }
}
}
3. request_created
Published by: request-service Consumed by: notification-service
{
type: 'request_created',
payload: {
request_id: 'uuid',
requester_id: 'uuid',
community_id: 'uuid',
title: 'Need help with...'
}
}
4. user_joined_community
Published by: community-service Consumed by: notification-service
{
type: 'user_joined_community',
payload: {
user_id: 'uuid',
community_id: 'uuid',
role: 'member'
}
}
Publishing Events
// services/request-service/src/routes/matches.ts
import { publishEvent } from '../events/publisher';
await publishEvent('match_completed', {
match_id,
request_id,
requester_id,
responder_id
});
Consuming Events
// services/reputation-service/src/events/subscriber.ts
import Queue from 'bull';
import { awardKarmaForCompletedMatch } from '../services/standingProjector';
const completionQueue = new Queue('karmyq-completion-reputation', process.env.REDIS_URL);
completionQueue.process('match_completed', async (job) => {
const { payload } = job.data;
await awardKarmaForCompletedMatch(payload);
});
See TR-003: Event-Driven Architecture for details.
Database Schema
PostgreSQL Schemas (7)
1. auth
-- Users
auth.users (id, name, email, password_hash, bio, skills, ...)
2. community
-- Communities and membership
community.communities (id, name, description, type, created_by, ...)
community.memberships (id, user_id, community_id, role, joined_at, ...)
community.join_requests (id, user_id, community_id, status, message, ...)
community.norms (id, community_id, title, description, category, ...)
3. requests
-- Help requests, offers, matches
requests.help_requests (id, requester_id, title, description, status, urgency, ...)
requests.request_communities (request_id, community_id) -- junction table
requests.offers (id, request_id, responder_id, message, status, ...)
requests.matches (id, request_id, requester_id, responder_id, status, ...)
4. reputation
-- Karma and trust
reputation.karma_records (id, user_id, community_id, points, reason, ...)
reputation.trust_scores (id, user_id, community_id, score, ...)
reputation.badges (id, user_id, community_id, badge_type, awarded_at, ...)
5. notifications
-- Notifications and preferences
notifications.notifications (id, user_id, type, title, message, read, ...)
notifications.preferences (id, user_id, channel, type, enabled, ...)
notifications.global_preferences (id, user_id, email_digest, ...)
6. messaging
-- Conversations and messages
messaging.conversations (id, created_at, ...)
messaging.participants (conversation_id, user_id, joined_at, ...)
messaging.messages (id, conversation_id, sender_id, content, ...)
7. feed
-- Activity feed
feed.activities (id, user_id, community_id, type, data, created_at, ...)
Database Conventions
- Primary Keys: UUIDs (
uuid_generate_v4()) - Timestamps:
created_at,updated_at(auto-managed) - Foreign Keys:
requester_id,responder_id(notuser_id,helper_id) - Schema Prefixes: Always use
requests.help_requests, not justhelp_requests - RLS on All Tables: Except feed service read-only tables
Indexes
Strategic indexes on:
- Foreign keys (
user_id,community_id) - Status fields (
status) - Timestamps (
created_atfor sorting) - Lookup fields (
emailfor login)
See DATA_MODEL.md for complete schema documentation with ERD diagram.
Authentication & Authorization
JWT Structure
{
"userId": "uuid",
"email": "user@example.com",
"communities": [
{ "id": "uuid", "name": "Community A", "role": "admin" }
],
"iat": 1234567890,
"exp": 1234567890
}
⚠️ The membership array field is
communities, notcommunityMemberships, and each entry's id key isid, notcommunityId. Auth middleware MUST readuser.communities; the wrong field 403s.
Middleware Chain
// 1. Authenticate - Verify JWT
export const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
};
// 2. Extract Community Context
export const tenantMiddleware = (req, res, next) => {
const communityId = req.headers['x-community-context'];
const membership = (req.user.communities ?? []).find(
m => m.id === communityId
);
req.communityId = communityId;
req.role = membership?.role;
next();
};
// 3. Set Database Context
export const dbContextMiddleware = (pool) => async (req, res, next) => {
if (req.communityId) {
await pool.query(
'SET LOCAL app.current_community_id = $1',
[req.communityId]
);
}
next();
};
// 4. Require Role
export const requireRole = (roles) => (req, res, next) => {
if (!roles.includes(req.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
Usage in Services
// Public endpoint (no auth)
app.post('/register', registerHandler);
// Authenticated endpoint
app.get('/profile',
authMiddleware,
getProfileHandler
);
// Community-scoped endpoint
app.get('/requests',
authMiddleware,
tenantMiddleware,
dbContextMiddleware(pool),
getRequestsHandler
);
// Admin-only endpoint
app.post('/community/settings',
authMiddleware,
tenantMiddleware,
dbContextMiddleware(pool),
requireRole(['admin']),
updateSettingsHandler
);
See FR-001: Authentication for details.
API Patterns
Standard Response Format
{
"success": true,
"data": { ... } | [ ... ],
"message": "Optional success message"
}
Error Response Format
{
"success": false,
"message": "Email is required",
"error": "VALIDATION_ERROR",
"details": { ... }
}
Pagination
{
"success": true,
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"pages": 5
}
}
Common HTTP Status Codes
200- Success201- Created400- Bad Request (validation error)401- Unauthorized (no/invalid token)403- Forbidden (insufficient permissions)404- Not Found409- Conflict (duplicate resource)500- Internal Server Error
Health Check Endpoints
Every service exposes:
GET /health
Response: { "status": "healthy", "service": "auth-service" }
Monorepo Structure
Directory Layout
karmyq/
├── apps/
│ ├── frontend/ # Next.js web app (Port 3000)
│ └── mobile/ # React Native + Expo
├── services/
│ ├── _template/ # Service template
│ ├── auth-service/ # Port 3001
│ ├── community-service/ # Port 3002
│ ├── request-service/ # Port 3003
│ ├── reputation-service/# Port 3004
│ ├── notification-service/ # Port 3005
│ ├── messaging-service/ # Port 3006
│ └── cleanup-service/ # Port 3008
├── packages/
│ └── shared/ # Shared middleware, types, utils
├── infrastructure/
│ ├── docker/ # Docker Compose files
│ ├── postgres/ # Database init scripts
│ └── observability/ # Grafana, Loki, Prometheus configs
├── tests/ # Integration and E2E tests
├── docs/ # Documentation
└── scripts/ # Automation scripts
Turborepo
The monorepo uses Turborepo for:
- Parallel builds - Build multiple services simultaneously
- Dependency tracking - Only rebuild what changed
- Caching - Speed up builds with intelligent caching
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
},
"dev": {
"cache": false
}
}
}
Shared Packages
packages/shared/
- middleware/ - Auth, tenant, rate limiting, validation
- types/ - TypeScript interfaces
- utils/ - Logger, helpers
- api/ - API client for frontend
- constants/ - Shared constants
Services import shared code:
import { authMiddleware, tenantMiddleware } from '@shared/middleware';
import { logger } from '@shared/utils';
Technology Stack
Backend
- Runtime: Node.js 24 (
node:24-alpine, Active LTS; Sprint 122 / ADR-090 — every image, rootengines.nodeand CI'sNODE_VERSIONare held to the same major by a blocking gate. This line previously read "Node.js 20" while the services actually ran EOLnode:18-alpine.) - Framework: Express 5 (Sprint 122;
path-to-regexp8 +body-parser2 — async handler rejections auto-forward to the error middleware, andreq.queryis a getter) - Language: TypeScript
- Database: PostgreSQL 15
- Cache/Queue: Redis 7 + Bull
- Event Queue: Bull (Redis-backed)
Frontend
- Framework: Next.js 16
- UI: React 19
- Styling: Tailwind CSS v4 (CSS-first — the theme lives in
@themeinglobals.css; there is notailwind.config) - State: React Context + Hooks
- API Client: Fetch API with shared client
Mobile
- Framework: React Native
- Platform: Expo SDK 57 (see
apps/mobile/package.jsonfor the pinned set) - Navigation: Expo Router
- Storage: Async Storage
Infrastructure
- Containerization: Docker + Docker Compose
- Orchestration: Docker Compose (dev), Kubernetes (future)
- Reverse Proxy: Nginx (production)
Observability
- Logging: Winston → Loki
- Dashboards: Grafana
- Metrics: Prometheus (planned)
- Tracing: OpenTelemetry (planned)
Testing
- Unit Tests: Jest + TypeScript (163 tests, 98%+ coverage on business logic)
- Integration Tests: Jest + Supertest (126 passing)
- E2E Tests: Playwright (full user journeys)
- Load Tests: K6 (performance benchmarks)
- TDD Workflow: Red → Green → Refactor methodology
CI/CD
- Platform: GitHub Actions
- Workflows: Lint, test, build, deploy
Infrastructure
Docker Compose
Development Stack (docker-compose.yml)
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: karmyq_db
POSTGRES_USER: karmyq_user
POSTGRES_PASSWORD: dev_password
ports:
- "5432:5432"
volumes:
- ./infrastructure/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
# All 8 services
auth-service:
build: ./services/auth-service
ports:
- "3001:3001"
environment:
DATABASE_URL: postgresql://...
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
# ... (7 more services)
frontend:
build: ./apps/frontend
ports:
- "3000:3000"
Observability Stack (docker-compose.observability.yml)
services:
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
promtail:
image: grafana/promtail:latest
volumes:
- /var/log:/var/log
grafana:
image: grafana/grafana:latest
ports:
- "3011:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
Environment Variables
Each service requires:
# Server
PORT=3001
NODE_ENV=development
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/karmyq_db
# Redis
REDIS_URL=redis://localhost:6379
# JWT
JWT_SECRET=your-secret-here
JWT_EXPIRATION=7d
# Logging
LOG_LEVEL=info
See ENVIRONMENT_VARIABLES.md for complete reference.
Observability
Structured Logging
All services use Winston for structured logging:
import { logger } from '@shared/utils/logger';
logger.info('Request received', {
method: req.method,
path: req.path,
userId: req.user?.userId,
communityId: req.communityId
});
logger.error('Database error', {
error: err.message,
stack: err.stack,
query: 'SELECT ...'
});
Log Levels
- debug: Detailed troubleshooting info
- info: General operational events
- warn: Warning messages
- error: Error messages with stack traces
Grafana Dashboards
Access at: http://localhost:3011
Dashboards:
- Service logs (query by service, level, user, community)
- Error rates
- Request latency (planned)
- Queue metrics (planned)
Monitoring (Planned)
- Prometheus: Metrics collection
- Alerting: Grafana alerts for errors, high latency
- Tracing: OpenTelemetry for distributed tracing
See operations/logging-and-monitoring.md for details.
Design Decisions
Why Microservices?
Pros:
- Independent development and deployment
- Technology flexibility per service
- Scalability (scale what needs scaling)
- Clear boundaries and ownership
Cons (accepted trade-offs):
- Complexity in orchestration
- Distributed debugging harder
- Eventual consistency challenges
Why Row-Level Security?
Pros:
- Database-level enforcement (cannot be bypassed)
- No application-level filtering needed
- Automatic isolation
- Audit trail
Cons (accepted trade-offs):
- Complex queries harder to debug
- Stats queries may need RLS disabled
- Testing requires proper setup
Why Events over REST?
Pros:
- Loose coupling between services
- Asynchronous processing
- Retry on failure
- Multiple subscribers per event
- Foundation for event sourcing
Cons (accepted trade-offs):
- Eventual consistency
- Monitoring distributed events
- Event versioning strategy needed
Why Server-Sent Events (SSE) over WebSocket?
Pros:
- Simpler (unidirectional)
- Auto-reconnect built-in
- HTTP/2 multiplexing
- Firewall-friendly
Cons (accepted trade-offs):
- No client → server messaging (use REST)
- Browser connection limits (6 per domain)
Scaling Considerations
Current Limitations (Single-Server)
- PostgreSQL on single instance
- Redis on single instance
- No horizontal scaling of services
- No load balancing
Future Scaling Strategy
Database
- Read Replicas: For feed service, read-heavy queries
- Connection Pooling: PgBouncer for connection management
- Partitioning: Partition large tables by community_id
Services
- Horizontal Scaling: Run multiple instances behind load balancer
- Sticky Sessions: For SSE connections (or use Redis pub/sub)
- Service Mesh: Istio/Linkerd for advanced routing
Queue
- Redis Cluster: For high availability
- Multiple Workers: Scale Bull workers independently
Caching
- Application Cache: Redis for user/community lookups
- CDN: CloudFront/Cloudflare for frontend assets
Security
Current Security Measures
- ✅ JWT authentication on all services
- ✅ Password hashing (bcrypt, 10 rounds)
- ✅ SQL injection prevention (parameterized queries)
- ✅ Row-Level Security (RLS)
- ✅ Rate limiting (all endpoints)
- ✅ CORS configuration
- ✅ Environment variables for secrets
Security Gaps (Planned)
- ⚠️ No refresh tokens (JWT expires but no rotation)
- ⚠️ No email verification flow
- ⚠️ No password reset flow
- ⚠️ No 2FA/MFA support
- ⚠️ SSE endpoint has no authentication (userId in URL only)
Performance
Current Optimizations
- ✅ Database connection pooling (max 20 connections per service)
- ✅ Redis caching for event queue
- ✅ Indexes on all foreign keys and lookup fields
- ✅ Efficient RLS policies
Performance Considerations
- Password Hashing: bcrypt is CPU-intensive (consider worker threads)
- RLS Overhead: Adds ~5-10ms per query (acceptable for security)
- No Application Caching: All queries hit database (consider Redis cache)
Related Documentation
-
Requirements:
-
Guides:
-
Operations:
Version: 10.11.0 Last Updated: 2026-06-05 Maintained by: Karmyq Development Team
Dependency Graph
Service Dependency Graph
Generated: 2026-10-05T13:35:05.965Z
Legend
- 🔴 Red nodes: Critical services (system fails if down)
- 🟡 Yellow nodes: Important services (degraded experience if down)
- ⚪ White nodes: Optional services
- 🔘 Gray nodes: Deprecated services
- Solid arrows: Service dependencies
- Dotted arrows: Infrastructure dependencies
graph TD
auth-service["auth-service\nPort: 3001\ncritical"]:::critical
community-service["community-service\nPort: 3002\ncritical"]:::critical
request-service["request-service\nPort: 3003\ncritical"]:::critical
reputation-service["reputation-service\nPort: 3004\ncritical"]:::critical
notification-service["notification-service\nPort: 3005\nimportant"]:::important
messaging-service["messaging-service\nPort: 3006\ncritical"]:::critical
cleanup-service["cleanup-service\nPort: 3008\nimportant"]:::important
geocoding-service["geocoding-service\nPort: 3009\noptional"]
social-graph-service["social-graph-service\nPort: 3010\ncritical"]:::critical
simulation-service["simulation-service\nPort: N/A\noptional"]
community-service --> auth-service
request-service --> auth-service
request-service --> community-service
request-service --> social-graph-service
reputation-service --> auth-service
notification-service --> auth-service
messaging-service --> auth-service
messaging-service --> community-service
social-graph-service --> auth-service
social-graph-service --> community-service
simulation-service --> all
auth-service -.-> postgres[(postgres)]
auth-service -.-> redis[(redis)]
community-service -.-> postgres[(postgres)]
community-service -.-> redis[(redis)]
community-service -.-> bull-queue[(bull-queue)]
request-service -.-> postgres[(postgres)]
request-service -.-> redis[(redis)]
request-service -.-> bull-queue[(bull-queue)]
reputation-service -.-> postgres[(postgres)]
reputation-service -.-> redis[(redis)]
reputation-service -.-> bull-queue[(bull-queue)]
notification-service -.-> postgres[(postgres)]
notification-service -.-> redis[(redis)]
notification-service -.-> bull-queue[(bull-queue)]
messaging-service -.-> postgres[(postgres)]
messaging-service -.-> redis[(redis)]
cleanup-service -.-> postgres[(postgres)]
cleanup-service -.-> redis[(redis)]
geocoding-service -.-> postgres[(postgres)]
social-graph-service -.-> postgres[(postgres)]
social-graph-service -.-> redis[(redis)]
classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
classDef important fill:#ffd93d,stroke:#f08c00,color:#000
classDef deprecated fill:#868e96,stroke:#495057,color:#fff