• English
  • SalonCloud — Quickstart

    SalonCloud is a monorepo of independently-deployable .NET 10 microservices that form the cloud backend for a salon POS platform. It receives incremental uploads from a Flutter store app, lands them in PostgreSQL (AWS RDS), and exposes typed, PII-governed read models to downstream services (booking, messaging, push, admin).

    What this platform does

    • Receives store data — the Flutter POS app uploads row-level changes (appointments, tickets, customers, payments, staff, services, settings) via POST /v1/sync/batch
    • Lands idempotently — last-write-wins (LWW) upsert on (changed_at, seq) with deduplication, clock-skew clamping, and suppression detection
    • Publishes read modelsdata_hub schema provides security_invoker PostgreSQL views with typed, null-safe projections and PII-tiered access roles
    • Powers booking — consumer-facing booking API (availability, OTP, holds, bookings, self-service management)
    • Drives operations — operator admin console (store management, RBAC, diagnostics, tasks) via Admin BFF and Engine health/rule engine
    • Routes notifications — push nudges and OTP/email/SMS messaging

    Prerequisites

    • .NET 10 SDK (pinned in global.json)
    • Docker — required for integration tests (Testcontainers spins up PostgreSQL) and for docker compose
    • Node.js — only needed for CDK synth tests (JSII)

    Quick start

    # Start all services locally
    docker compose up --build
    
    # The edge proxy listens on :8080
    curl localhost:8080/healthz    # Accounts (catch-all route)
    
    # Run all tests (unit + integration)
    dotnet test

    The API auto-applies EF migrations on startup (RunMigrationsOnStartup, default on).

    Service inventory

    ServiceProjectPurpose
    Ingestionsrc/SalonCloud.IngestionHot landing path — receives store sync uploads
    Accountssrc/SalonCloud.AccountsStore onboarding, auth, device registration, capability publishing
    Bookingsrc/SalonCloud.BookingPublic consumer booking API (availability, OTP, holds, bookings)
    Adminsrc/SalonCloud.AdminOperator ops console BFF (store/device/RBAC/operator management)
    Enginesrc/SalonCloud.EngineInternal health snapshots + rule-based task generation
    Pushsrc/SalonCloud.PushPush notification delivery to store devices
    Messagingsrc/SalonCloud.MessagingEmail/SMS gateway (Twilio, Gmail SMTP)
    Observabilitysrc/SalonCloud.ObservabilityLog upload, crash bundles, device commands
    DataHubsrc/SalonCloud.DataHub.InfrastructureMigration-only project — data_hub schema views
    Infrastructuresrc/SalonCloud.InfrastructureEF migrations for ingestion schema
    Mediasrc/SalonCloud.MediaShared S3/local media storage abstraction
    Sharedsrc/SalonCloud.SharedCross-cutting: capability poller, telemetry

    Each service has a companion *.Infrastructure project for its data layer (migrations, repositories, bridges). Infrastructure projects exist for: Ingestion, Accounts, Booking, Admin, Engine, Push, Messaging, Observability, and DataHub.

    Project layout

    /
    ├── src/                           # All microservices
    │   ├── SalonCloud.Ingestion/      # ASP.NET Core API
    │   ├── SalonCloud.Accounts/
    │   ├── SalonCloud.Booking/
    │   ├── SalonCloud.Admin/
    │   ├── SalonCloud.Engine/
    │   ├── SalonCloud.Push/
    │   ├── SalonCloud.Messaging/
    │   ├── SalonCloud.Observability/
    │   ├── SalonCloud.*.Infrastructure/  # Data layers (9 projects)
    │   ├── SalonCloud.DataHub.Infrastructure/
    │   ├── SalonCloud.Media/
    │   └── SalonCloud.Shared/
    ├── infra/SalonCloud.Infra/        # AWS CDK (C#)
    ├── tests/SalonCloud.Tests/        # xUnit + Testcontainers
    ├── docker/                        # nginx proxy config, pgAdmin setup
    ├── tools/                         # Dev seed SQL, UAT deploy helper
    └── docker-compose.yml             # Local multi-service orchestration

    Key design principles

    • One service = one bounded context = one PostgreSQL schema. Never cross schemas directly. The sole exception: data_hub is the blessed shared read substrate via security_invoker views.
    • Pre-production — no live customers, no production data. Freely squash migrations, rename tables, wipe DB. No backward-compatibility shims.
    • Pull-based capability sync — services poll Accounts for store state rather than receiving pushed events. Downed services catch up naturally.
    • Row-level LWW idempotency — upsert guarded by (changed_at, seq), not batch id. The C# deduplication ordering must match the SQL guard exactly.

    Critical invariants

    These are the silent-breakage risks — read before modifying core pipeline code:

    1. store_id is uuid, device_id is text (not guaranteed UUID)
    2. changed_at is unix milliseconds from the sync envelope; payload DateTime is unix seconds — different scales
    3. LWW dedupe must match SQL guard ordering — divergence = silent data loss
    4. Landing must never silently drop a rowRETURNING keys, suppression detection, WARN logs
    5. A cast in data_hub views must never raise — always use try_* functions (try_bigint, try_int, try_numeric, try_timestamp, try_jsonb)
    6. Consumers must IsDBNull-guard nullable columns AND trace WHERE/JOIN predicates — three-valued logic can silently drop rows before C# sees them
    7. delete writes a tombstone (op='delete', payload=NULL), never a hard delete

    Where to go next

    • Architecture — service catalog, cross-service patterns, design decisions
    • Data Flow — ingestion pipeline, DataHub, capability mirror, booking flow
    • Operations — Docker, CI/CD, AWS CDK, configuration
    • Testing — test framework, integration patterns, test categories