Architecture Deployment API

Deployment View

Describes the hardware and software environments in which the system runs, how software is mapped to infrastructure, and how components communicate across deployment nodes.

Development Environment — Docker Compose

During local development the entire infrastructure stack runs inside a single Docker bridge network (flowershop-dev). The API container connects to PostgreSQL, Redis, Mosquitto MQTT, RabbitMQ, and Keycloak within that network. The three portal processes run outside Docker (plain dotnet run) and reach the API on the exposed host port 8080. pgAdmin and Redis Commander are optional tooling containers activated with --profile tools and are never started by default.

flowchart TD
  subgraph HostMachine["Developer Workstation (Windows 11)"]
    subgraph DockerNetwork["Docker Bridge Network: flowershop-dev (172.21.0.0/16)"]
      PG["postgres-dev\nPostgreSQL 15-alpine\n:5432\nDB: flowershop_iot_dev\n     flowershop_iot_read_dev\n     keycloak"]
      REDIS["redis-dev\nRedis 7-alpine\n:6379\nAppendonly persistence"]
      MQ["mqtt-dev\neclipse-mosquitto 2.0\n:1883 (MQTT)\n:9001 (WebSocket)"]
      RMQ["rabbitmq-dev\nRabbitMQ 3-management\n:5672 (AMQP)\n:15672 (Mgmt UI)"]
      KC["keycloak-dev\nKeycloak 23.0\n:8090→8080\nRealm: flowershop\nDB: postgres-dev/keycloak"]
      API["api-dev\n.NET 8 / ASP.NET Core\n:8080\n(dotnet watch — hot reload)"]
    end

    subgraph Portals["Portal Processes (dotnet run — separate terminals)"]
      ADM["AdminPortal :5001\nRazor Pages"]
      VND["VendorPortal :5002\nRazor Pages"]
      CUS["CustomerApp :5003\nASP.NET MVC"]
    end

    subgraph Tools["Optional tooling (--profile tools)"]
      PGA["pgAdmin :5050"]
      RCC["redis-commander :8081"]
    end
  end

  API -->|TCP :5432| PG
  API -->|TCP :6379| REDIS
  API -->|MQTT :1883| MQ
  MQ -->|MQTT| API
  API -->|AMQP :5672| RMQ
  API -->|HTTP :8080| KC
  ADM -->|HTTP :8080| API
  VND -->|HTTP :8080| API
  CUS -->|HTTP :8080| API
  PGA -->|:5432| PG
  RCC -->|:6379| REDIS

Sub-domain Mapping — Docker Compose Containers

Container Business Sub-domain Role in the platform
api-dev (.NET 8 / ASP.NET Core :8080) All sub-domains The single deployable backend unit: REST API, SignalR hub, MediatR pipeline, background services
postgres-dev (PostgreSQL 15 :5432) All sub-domains Persistent store for all aggregates, the outbox table, and the Keycloak realm database
redis-dev (Redis 7 :6379) Identity & Tenancy, Customer Purchasing, Payments Token blacklist, idempotency keys, query result cache
mqtt-dev (Mosquitto :1883/:9001) IoT / MQTT Message broker for SmartVase telemetry (heartbeats, sensor readings)
rabbitmq-dev (RabbitMQ :5672) All sub-domains (async events — pending EP-09) Future cross-service event bus; currently a stub — InMemoryEventBus is active
keycloak-dev (Keycloak 23 :8090) Identity & Tenancy OIDC identity provider issuing JWTs with vendor_id, vendor_type, customer_id claims
AdminPortal (Razor Pages :5001) Platform Administration Vendor and vase management UI; calls api-dev over HTTP
VendorPortal (Razor Pages :5002) Vendor Management / Vendor Order Fulfilment Bouquet catalogue and order fulfilment UI; calls api-dev over HTTP
CustomerApp (ASP.NET MVC :5003) Customer Purchasing Bouquet browsing, cart, and checkout UI; calls api-dev over HTTP

Container Startup Dependencies

flowchart LR
  PG["postgres-dev\n(healthcheck: pg_isready)"]
  REDIS["redis-dev\n(healthcheck: redis-cli ping)"]
  MQ["mqtt-dev\n(healthcheck: mosquitto_sub)"]
  KC["keycloak-dev\n(depends: postgres-dev healthy)"]
  API["api-dev\n(depends: postgres, redis, mqtt, keycloak healthy)"]

  PG --> KC
  PG --> API
  REDIS --> API
  MQ --> API
  KC --> API

Sub-domain Mapping — Startup Dependency Chain

Dependency Business Sub-domain Why it must be healthy first
postgres-devkeycloak-dev Identity & Tenancy Keycloak stores its realm, users, and client configuration in PostgreSQL
postgres-devapi-dev All sub-domains API cannot start without a reachable database — all aggregates and the outbox table live here
redis-devapi-dev Identity & Tenancy, Customer Purchasing Token blacklist and idempotency middleware initialise at startup
mqtt-devapi-dev IoT / MQTT MqttDeviceStartupService subscribes to vase topics on startup; broker must be available
keycloak-devapi-dev Identity & Tenancy JWT validation fetches Keycloak OIDC discovery document on first request

Port Map

Service Host Port Container Port Protocol
REST API + Swagger 8080 8080 HTTP
Admin Portal 5001 5001 HTTP
Vendor Portal 5002 5002 HTTP
Customer Web App 5003 5003 HTTP
Keycloak Admin Console 8090 8080 HTTP
PostgreSQL 5432 5432 TCP
Redis 6379 6379 TCP
MQTT (raw) 1883 1883 MQTT
MQTT (WebSocket) 9001 9001 WS
RabbitMQ AMQP 5672 5672 AMQP
RabbitMQ Management 15672 15672 HTTP
pgAdmin 5050 80 HTTP
Redis Commander 8081 8081 HTTP

Logical Deployment Tiers

The production topology separates clients, application servers, infrastructure services, and external SaaS into four tiers. The SmartVase firmware is a Tier 1 client that communicates directly with the Mosquitto broker in Tier 3 over MQTT — it never touches the REST API. All web clients (mobile app, web portals) communicate exclusively with Tier 2 application servers. External SaaS providers (Stripe, Firebase, Nominatim, Twilio) are consumed from Tier 2 only.

flowchart TB
  subgraph Tier1["Tier 1 — Client"]
    MOB["Mobile App\n(React Native / Flutter)"]
    WEB["Customer Web Browser\n(:5003)"]
    VADM["Vendor Browser\n(:5002)"]
    AADM["Admin Browser\n(:5001)"]
    VASE["SmartVase Firmware\n(MQTT over TCP)"]
  end

  subgraph Tier2["Tier 2 — Application"]
    API2["REST API\n(.NET 8 / Kestrel :8080)\n+ SignalR Hub (in-process)"]
    ADM2["Admin Portal\n(Razor Pages :5001)"]
    VND2["Vendor Portal\n(Razor Pages :5002)"]
    CUS2["Customer App\n(MVC :5003)"]
  end

  subgraph Tier3["Tier 3 — Infrastructure"]
    PG2["PostgreSQL 15\n(primary + read replica)"]
    RD2["Redis 7\n(cache + blacklist + idempotency)"]
    KC2["Keycloak 23\n(OIDC / JWT)"]
    MQ2["Mosquitto MQTT\n(IoT broker)"]
    RMQ2["RabbitMQ\n(event bus — pending EP-09)"]
  end

  subgraph Tier4["Tier 4 — External SaaS"]
    STR["Stripe\n(payments)"]
    FCM["Firebase FCM\n(push notifications)"]
    TWI["Twilio\n(SMS)"]
    NOM["Nominatim OSM\n(geocoding)"]
  end

  Tier1 --> Tier2
  Tier2 --> Tier3
  Tier2 --> Tier4
  VASE --> MQ2

Sub-domain Mapping — Deployment Tiers

Tier Component Business Sub-domain
Tier 1 — Client Mobile App / Customer Web Browser Customer Purchasing
Tier 1 — Client Vendor Browser (:5002) Vendor Management / Vendor Order Fulfilment
Tier 1 — Client Admin Browser (:5001) Platform Administration
Tier 1 — Client SmartVase Firmware (MQTT) IoT / MQTT
Tier 2 — Application REST API + SignalR (:8080) All sub-domains
Tier 2 — Application Admin / Vendor / Customer Portals Platform Administration, Vendor Management, Customer Purchasing
Tier 3 — Infrastructure PostgreSQL All sub-domains (persistence)
Tier 3 — Infrastructure Redis Identity & Tenancy, Customer Purchasing, Payments (cache / blacklist / idempotency)
Tier 3 — Infrastructure Keycloak Identity & Tenancy
Tier 3 — Infrastructure Mosquitto MQTT IoT / MQTT
Tier 3 — Infrastructure RabbitMQ All sub-domains (async events — pending EP-09)
Tier 4 — External SaaS Stripe Payments
Tier 4 — External SaaS Firebase FCM Customer Purchasing (push notifications)
Tier 4 — External SaaS Twilio Customer Purchasing (SMS)
Tier 4 — External SaaS Nominatim OSM Customer Purchasing (geo-coding for bouquet search)

Cloud Deployment — Dev/Staging Environment

The deployed dev/staging environment uses free-tier managed services to avoid running VMs. All application processes are containerised; infrastructure services are either managed PaaS or replaced by SaaS equivalents.

Hosting Decisions

Component Platform URL Deploy mechanism
REST API (.NET 8) Fly.io https://api.findmyflowers.pl flyctl deploy via fly.api.toml
Keycloak 23 Fly.io https://auth.findmyflowers.pl flyctl deploy via fly.keycloak.toml
Customer App (MVC) Fly.io https://app.findmyflowers.pl flyctl deploy via fly.customer-app.toml
Admin Portal (Razor Pages) Render https://admin.findmyflowers.pl Image pushed to GHCR → Render deploy API
Vendor Portal (Razor Pages) Render https://vendor.findmyflowers.pl Image pushed to GHCR → Render deploy API
MQTT broker HiveMQ Cloud <cluster>.s1.eu.hivemq.cloud:8883 Managed SaaS — no deploy step

MQTT — HiveMQ Cloud Free Tier

Local development continues to use the Mosquitto container in docker-compose (plain TCP, port 1883, no TLS). In deployed environments Mosquitto is replaced by HiveMQ Cloud (free tier).

Free tier limits: 100 concurrent connections, 10 GB/month data transfer, no uptime SLA.
Protocol: MQTT 3.1.1 / 5.0, QoS 0/1/2, persistent sessions. TLS only — port 8883 (plain 1883 not available).

Required backend code changes before EP-15 (not yet implemented):

  1. MqttConfiguration POCO — add bool UseTls property (default false)
  2. MqttService.cs — add .WithTlsOptions(o => o.UseTls()) to the MqttClientOptionsBuilder when UseTls = true
  3. appsettings.Production.json — set MQTT:BrokerHost, MQTT:BrokerPort = 8883, MQTT:UseTls = true; credentials injected via Fly.io secrets (MQTT__Username, MQTT__Password)
  4. SmartVase firmware — must connect to HiveMQ Cloud on port 8883/TLS instead of Mosquitto on 1883 (firmware-side change)

CI/CD — GitHub Actions

All deployments trigger on push to main via .github/workflows/deploy-*.yml. Secrets required:

Secret Used by
FLY_API_TOKEN All Fly.io deploys
RENDER_API_KEY Render portal deploys
RENDER_ADMIN_SERVICE_ID Admin Portal Render service
RENDER_VENDOR_SERVICE_ID Vendor Portal Render service
MQTT__Username / MQTT__Password Fly.io API app — injected at runtime via fly secrets set

Environment Variables (key settings)

Variable Purpose
ASPNETCORE_ENVIRONMENT Development / Production
Authentication__UseKeycloak false → dev-token mode (no Keycloak required)
ConnectionStrings__DefaultConnection PostgreSQL primary
ConnectionStrings__ReadConnection PostgreSQL read replica
ConnectionStrings__Redis Redis (host:port); if absent → AddDistributedMemoryCache() fallback
MQTT__BrokerHost / MQTT__BrokerPort Mosquitto endpoint
Authentication__Keycloak__Authority Keycloak realm URL
JWT__SecretKey Dev-only JWT signing key

Volumes (persistent data)

Volume Contents
postgres_dev_data PostgreSQL data directory
redis_dev_data Redis AOF log
mosquitto_dev_data MQTT persistence
rabbitmq_dev_data RabbitMQ mnesia database
api_dev_logs ASP.NET Core log files