Skip to main content

System Architecture

High-level architecture and design decisions for the CRM & Marketing Platform.

Architecture Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL CLIENTS │
│ (Admin UI, Mobile App, Staff Portal, Club Dashboard) │
└─────────────────────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────────────┐
│ CRM & MARKETING PLATFORM │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ REST/ │ │ Identity │ │ Campaign │ │ Social │ │
│ │ GraphQL │ │ Resolution │ │ Orchestrator│ │ Publisher │ │
│ │ API │ │ Engine │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Activity │ │ Engagement │ │ Journey │ │ Event │ │
│ │ Timeline │ │ Scoring │ │ Engine │ │ Promoter │ │
│ │ Aggregator │ │ Engine │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ └────────────────┴────────────────┴────────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ CRM Database │ │
│ │ (PostgreSQL) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Messaging │ │ Social APIs │ │ SCL / TeeTime │
│ Service │ │ (FB, IG, X) │ │ (Events) │
└───────────────┘ └───────────────┘ └───────────────┘

Technology Stack

ComponentTechnologyRationale
APINestJS + GraphQLConsistent with existing services
DatabasePostgreSQLShared SCL database with schema=crm
ORMPrismaConsistent with existing services
Event TransportRedis + BullMQAlready in use
CacheRedisFast profile lookups
SearchPostgreSQL FTSStart simple
Social APIsOfficial SDKsFacebook, Instagram, Twitter
Job SchedulingBullMQ + cronCampaign scheduling

Database Architecture

CRM uses the SCL PostgreSQL database with a dedicated crm schema for data isolation.

Why Shared Database?

  1. Operational simplicity: Single database instance to manage
  2. Cost efficiency: No separate database provisioning
  3. Schema isolation: CRM tables are fully isolated in crm schema
  4. Cross-schema queries: Future potential for direct joins if needed

Connection URL Pattern

The CRM_DATABASE_URL includes the schema parameter:

postgresql://user:pass@scl-db.internal:5432/scl?schema=crm

Prisma Schema Configuration

// libs/prisma/crm-client/prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("CRM_DATABASE_URL") // Includes ?schema=crm
}

Schema Boundaries

SchemaOwnerTables
publicSCLMembers, Clubs, Tiers, Payments
crmCRMCustomerProfiles, Segments, Campaigns, Activities

Important: CRM should never directly query SCL tables. All data flows via events.

Service Boundaries

ServiceResponsibilityOwns
CRM/Marketing PlatformWHO, WHAT, WHEN, WHEREProfiles, Segments, Campaigns, Automation, Social
Messaging ServiceHOW (delivery)Templates, Multi-channel delivery, Consent, Providers
SCL ServiceMembership dataMembers, Tiers, Payments
TeeTime ServiceGolf dataPlayers, Bookings, Tournaments

Key Design Decisions

1. CRM Owns Segmentation

CRM defines segments using rich cross-service data. Messaging executes delivery.

CRM                                    Messaging
┌─────────────────────┐ ┌─────────────────────┐
│ CustomerSegment │ │ Segment (simplified)│
│ ─────────────────── │ sync │ ─────────────────── │
│ Rules using: │ ──────────────▶│ Contact membership │
│ - SCL tier │ │ only (for delivery) │
│ - TeeTime bookings │ │ │
│ - CRM engagement │ │ │
└─────────────────────┘ └─────────────────────┘

Messaging service owns consent (GDPR/POPIA compliance). CRM denormalizes opt-in flags for query convenience but does NOT manage consent.

3. Event-Driven Sync

All data flows via events from source services. CRM never queries SCL/TeeTime directly.

4. Denormalization

Frequently queried data (activity counts, opt-in flags, golf handicap) denormalized onto CustomerProfile for performance.

Data Flow

Inbound Events

  1. Source service (SCL/TeeTime/Messaging) publishes event
  2. Event lands in crm-events BullMQ queue
  3. Worker processes event:
    • Resolves identity
    • Updates CustomerProfile
    • Creates Activity record
    • Triggers scoring (async)
    • Updates segment membership (async)

Outbound Events

EventTriggerConsumers
crm.profile.createdNew profileAnalytics
crm.profile.mergedDuplicate mergeMessaging
crm.segment.membership.changedSegment refreshMessaging
crm.churn.risk.highRisk score > 80Alerting

Performance Requirements

MetricTarget
Profile lookupUnder 100ms p99
Timeline queryUnder 500ms p99
Segment refreshUnder 30 seconds for 10k members
Campaign schedulingUnder 5 seconds
Event processingUnder 5 seconds