{
  "markdown": "# HelpDesk\n\n> **Hard service boundaries now. Broker infrastructure when it earns its keep.**\n\nHelpDesk is a working demonstration of **Brokerless Microservice Mesh Architecture (BMMA)** built with .NET 10, FastEndpoints, MongoDB, Aspire, and a SvelteKit BFF. It explores a deliberate middle path for systems that have outgrown purely in-process boundaries but do not yet need Kafka, RabbitMQ, Azure Service Bus, or the operational model that comes with them.\n\nBMMA Higlights:\n\n- Business capabilities run in separate service processes\n- Each service owns its code, logical database, REST surface, and failure handling\n- Services collaborate through explicit, past-tense contract events\n- No service calls another service's REST API to complete an internal workflow\n- No central message broker is required\n\n## Why this architecture exists\n\nA modular monolith is often the right place to start. A brokered microservice platform is sometimes the right place to end up. The expensive mistake is assuming those are the only two choices. Typical characteristics look like this:\n\n|                      | Modular monolith                  | Brokerless mesh                            | Brokered microservices                                             |\n|----------------------|-----------------------------------|--------------------------------------------|--------------------------------------------------------------------|\n| Runtime boundary     | One process                       | Separate processes on one host             | Separate processes, often across hosts                             |\n| Collaboration        | In-process calls/events           | Direct event links with persisted queues   | Events through a central broker                                    |\n| Data ownership       | Often shared, ideally modular     | Logically service-owned                    | Service-owned, often credential-isolated                           |\n| Boundary enforcement | Primarily architecture discipline | Project and process boundaries             | Project, process, network, and persistence boundaries              |\n| Operational cost     | Usually lowest                    | Moderate                                   | Usually highest                                                    |\n| Best fit             | One deployment is enough          | Strong boundaries before distributed scale | Independent scale, multi-host resilience, rich broker capabilities |\n\nThe brokerless mesh is useful when the **shape of microservices** is valuable before the **infrastructure of distributed microservices** is justified.\n\nThat gives a team several practical benefits:\n\n1. **Important boundaries are executable, not aspirational.** A service cannot reach into another service's DI container, domain entities, or persistence abstractions through a project reference. Database ownership remains a convention in the included shared-MongoDB topology.\n2. **Business workflows are already asynchronous.** Adding a broker later does not require first untangling synchronous service call chains.\n3. **Local development stays approachable.** Aspire starts the application, MongoDB, service processes, and frontend as one observable resource graph.\n4. **The system pays for complexity gradually.** Network partitions, broker clusters, schema registries, and independent scaling arrive only when requirements make them worthwhile.\n\nThis is not an argument against brokers. It is an argument for introducing one at the point where its capabilities have a clear return.\n\n---\n\nThe following sections talk only of the user onboarding/profile management flows such as: registration, email verification, login, password reset, profiles, profile pictures, and notification jobs. The BMMA architecture can be easily understood by looking at the onboarding flows alone.\n\n> [!IMPORTANT]\n> The current mesh transport is **host-local IPC**. Local development runs the services as separate processes under Aspire. Production co-locates them in one backend container with a shared lifecycle. Project and process boundaries are enforced; persistence ownership uses separate logical databases but shared MongoDB credentials in the included topologies. This repository does not currently provide multi-host transport or independent service scaling.\n\n[Jump to the quickstart](#quickstart) if you want to run the system first.\n\n---\n\n## Architecture at a glance\n\n```mermaid\nflowchart LR\n  Browser[\"Browser\"]\n  BFF[\"SvelteKit BFF<br/>session and server-side API clients\"]\n\n  subgraph Mesh[\"Host-local backend mesh\"]\n    ID[\"UserIdentity<br/>credentials and identity lifecycle\"]\n    PR[\"UserProfile<br/>profile lifecycle and pictures\"]\n    NT[\"Notifications<br/>email jobs and local projections\"]\n\n    IDDB[(\"Identity database\")]\n    PRDB[(\"Profile database\")]\n    NTDB[(\"Notifications database\")]\n\n    ID -. \"identity events\" .-> PR\n    ID -. \"verification and reset events\" .-> NT\n    PR -. \"profile events\" .-> NT\n\n    ID --- IDDB\n    PR --- PRDB\n    NT --- NTDB\n  end\n\n  Browser -->|HTTPS| BFF\n  BFF -->|private REST| ID\n  BFF -->|private REST| PR\n```\n\nThe browser sees one application boundary. SvelteKit acts as a backend-for-frontend:\n\n- Backend origins remain server-only;\n- JWTs stay in an `HttpOnly` session cookie rather than browser JavaScript or storage;\n- **Identity** and **Profile** services expose private REST APIs to the BFF;\n- **Notifications** service has no public business API.\n\nInside the backend, REST stops at the owning service. Cross-service business flow only uses events.\n\n## The rules that make the mesh work\n\nThe architecture depends less on a particular library than on a small set of hard rules.\n\n### 1. Commit locally, then publish a fact\n\nEvents describe something that has already happened:\n\n```text\npersist identity\n    then broadcast UserIdentityRegisteredEvent\n    then broadcast UserIdentityVerificationIssuedEvent\n```\n\nThey are not commands asking another service to complete the publisher's transaction. If publishing or handling is delayed, the publisher's state remains internally valid.\n\n### 2. Subscribers change only what they own\n\nA subscriber may update its own database, maintain a local projection, or queue its own work. It does not call back into the publisher to finish the workflow.\n\nThis avoids distributed request chains such as:\n\n```text\nIdentity -> Profile -> Notifications -> Identity\n```\n\nThose chains look simple in a diagram but couple availability, latency, retries, and deployment order across every participant.\n\n### 3. Contracts are public language, not shared domain\n\nContract projects contain only what services need to communicate:\n\n```csharp\npublic sealed record UserIdentityRegisteredEvent(\n    string UserIdentityId,\n    string Email,\n    DateTime RegisteredAt) : IEvent;\n```\n\nThey do not contain persistence entities, stores, endpoints, handlers, SMTP logic, or a shared domain model.\n\n### 4. Dependency direction is enforced\n\n```mermaid\nflowchart TB\n  AH[\"Aspire AppHost<br/>(orchestration only)\"]\n  ID[\"Services.UserIdentity\"]\n  PR[\"Services.UserProfile\"]\n  NT[\"Services.Notifications\"]\n  CT[\"Contracts.*\"]\n  CM[\"Common.*<br/>infrastructure and generic helpers\"]\n\n  AH --> ID\n  AH --> PR\n  AH --> NT\n  ID --> CT\n  PR --> CT\n  NT --> CT\n  ID --> CM\n  PR --> CM\n  NT --> CM\n```\n\nThere are deliberately no project-reference arrows between service projects.\n\n```text\nAllowed:    AppHost      -> service hosts (orchestration only)\n            Services     -> Contracts, Common\n\nForbidden:  Service A    -> Service B\n            Contracts    -> Services\n            Common       -> service-specific behavior\n            Service REST -> another service's REST for internal workflows\n```\n\n## A workflow through the mesh\n\nRegistration shows how the rules compose into a business flow:\n\n```mermaid\nsequenceDiagram\n  participant BFF as SvelteKit BFF\n  participant ID as UserIdentity\n  participant PR as UserProfile\n  participant NT as Notifications\n\n  BFF->>ID: POST /identities/register\n  ID->>ID: Commit identity\n\n  par Profile branch\n    ID-->>PR: UserIdentityRegisteredEvent\n    PR->>PR: Create deactivated profile\n    PR-->>NT: UserProfileRegisteredEvent\n  and Notification branch\n    ID-->>NT: UserIdentityVerificationIssuedEvent\n    NT->>NT: Queue verification email\n  end\n\n  BFF->>ID: Verify email address\n  ID->>ID: Activate identity\n  ID-->>PR: UserIdentityVerifiedEvent\n  PR->>PR: Activate matching profile\n```\n\nEach dashed cross-service arrow is a contract event. The BFF calls are REST, and each solid self-call is local state or local work. No distributed transaction coordinates the flow. The same model handles password-reset email, profile activation, and the Notifications service's local display-name projection. Handlers are designed around local ownership and idempotent behavior where duplicate delivery matters.\n\n## What “brokerless” means here\n\nBrokerless does **not** mean messaging-free, in-memory, or best-effort by definition.\n\nHelpDesk uses FastEndpoints remote messaging:\n\n- Each publisher exposes a stable IPC endpoint\n- Publishers register event hubs and (optionally) known subscriber IDs\n- Subscribers map handlers to the publisher by service name\n- MongoDB-backed storage persists queued event records and subscriber state\n- Events are broadcast only after the publisher's local write succeeds\n\nThe wiring is explicit:\n\n```csharp\n// publisher transport\noptions.ListenInterProcess(UserIdentityService.Name);\n\n// publisher hub\nhandlers.RegisterEventHub<UserIdentityRegisteredEvent>();\n\n// subscriber\napp.MapRemote(UserIdentityService.Name, subscriber =>\n{\n    subscriber.Subscribe<UserIdentityRegisteredEvent, UserIdentityRegisteredEventHandler>();\n});\n```\n\nOnce an event has entered the queue, persisted records support delivery and retry without operating a separate broker. This design does not claim the complete feature set of Kafka, RabbitMQ, or a managed service bus. There is no claim of guaranteed publication, global ordering, exactly-once delivery, multi-region routing, or independently scalable consumers.\n\n## When this pattern fits\n\nConsider this architecture when:\n\n- One machine or one deployment unit can comfortably run the system\n- Code and domain ownership need stronger enforcement than ordinary module conventions\n- Asynchronous workflows are desirable\n- A central broker would be mostly operational/cost overhead today\n- You want contracts and handlers that can survive a later transport change\n\nPrefer a modular monolith when:\n\n- One process and one deployment are genuine advantages\n- Module boundaries can be maintained with code-level enforcement\n- Asynchronous cross-module workflows add little value\n- The team should optimize for the smallest possible operational surface\n\nIntroduce a broker or network-capable transport when:\n\n- Services must run or scale independently across hosts\n- Consumers need distributed coordination\n- Multi-region routing, replay tooling, high fan-out, or broker-specific delivery controls matter\n- Host-local availability and throughput are no longer sufficient\n\nGraduating to a broker is an architecture evolution, not a configuration toggle. The event contracts, ownership rules, and most handlers should remain useful, but transport semantics, retries, observability, security, and deployment topology must be engineered deliberately.\n\n## What the repository demonstrates\n\n| Component          | Owns                                                            | Communicates through                                              |\n|--------------------|-----------------------------------------------------------------|-------------------------------------------------------------------|\n| **UserIdentity**   | Credentials, verification, password reset, RSA JWT issuance     | Private REST consumed by the BFF; identity events to the mesh     |\n| **UserProfile**    | Profile lifecycle, display names, profile pictures              | Authenticated REST to BFF; identity subscriptions; profile events |\n| **Notifications**  | Email jobs, SMTP integration, local display-name projection     | Identity and profile subscriptions; no public business API        |\n| **SvelteKit BFF**  | Browser session boundary, forms, server-only API clients        | HTTPS to browser; private REST to Identity and Profile            |\n| **Aspire AppHost** | Local resource graph, startup ordering, configuration injection | Development orchestration only                                    |\n\nThe sample is intentionally an onboarding vertical slice, not a complete multi-domain helpdesk product.\n\n## Current runtime topology\n\nThe service boundaries and the deployment topology are separate concerns.\n\n### Local development\n\nAspire runs MongoDB, the three .NET service processes, and Vite. It injects connection strings and private service origins, manages startup order, and exposes logs and dynamically assigned application endpoints in the Aspire dashboard.\n\n### Production\n\nThe included Compose deployment targets a single VPS. Caddy is the only public edge. SvelteKit and MongoDB remain private. The three .NET services run as separate child processes inside one backend container so FastEndpoints IPC remains host-local.\n\n```mermaid\nflowchart LR\n  Internet((Internet))\n  Caddy[\"Caddy<br/>HTTPS edge\"]\n  BFF[\"SvelteKit BFF\"]\n\n  subgraph Backend[\"Backend container, shared lifecycle\"]\n    ID[\"UserIdentity process\"]\n    PR[\"UserProfile process\"]\n    NT[\"Notifications process\"]\n\n    ID -. \"IPC events\" .-> PR\n    ID -. \"IPC events\" .-> NT\n    PR -. \"IPC events\" .-> NT\n  end\n\n  Mongo[(\"MongoDB<br/>service-owned databases\")]\n\n  Internet --> Caddy\n  Caddy --> BFF\n  BFF -->|private REST| ID\n  BFF -->|private REST| PR\n  ID --> Mongo\n  PR --> Mongo\n  NT --> Mongo\n```\n\nThis topology preserves code and process boundaries plus separate service-owned logical databases. Database ownership is not credential-isolated because the child processes share the production MongoDB connection. The backend services also share a deployment and lifecycle boundary. Splitting them across machines or containers requires switching to the \"remote/gRPC\" transport (instead of IPC) and corresponding deployment design which is not covered by the sample.\n\n## Quickstart\n\n### Prerequisites\n\n- .NET 10 SDK\n- Node.js 26 or newer (`.node-version` selects 26.4.0)\n- pnpm 11 or newer (`packageManager` selects 11.10.0)\n- an Aspire-compatible container runtime\n\n### Install and run\n\n```bash\ncorepack enable\ncorepack prepare pnpm@11.10.0 --activate\n# If Corepack is unavailable: npm install --global pnpm@11.10.0\n\npnpm install --frozen-lockfile\npnpm stack:dev\n```\n\n`pnpm stack:dev` starts the full local application through `backend/AppHost`:\n\n- Ephemeral authenticated MongoDB on `localhost:27017`\n- UserIdentity, UserProfile, and Notifications\n- The SvelteKit/Vite frontend\n- The Aspire dashboard\n\nApplication HTTP ports are assigned dynamically. Open the frontend and service endpoints from the Aspire dashboard. Stop the stack with Ctrl+C.\n\nDevelopment suppresses SMTP delivery. After registering or requesting a password reset, open the Notifications resource logs in the Aspire dashboard and follow the logged verification or reset link.\n\nMatching development-only RSA JWT material is committed in the backend appsettings so a fresh clone starts without secret generation. Never reuse those keys outside development. Production must override the Identity private key and Profile public key as a matching pair.\n\nIdentity and Profile expose OpenAPI at `/openapi/v1.json` and Scalar at `/scalar` outside Production.\n\n### Validate the repository\n\nThe quick check covers frontend type checks, linting, formatting, and unit tests:\n\n```bash\npnpm check:quick\n```\n\nBefore the first full check, install Playwright's browser binaries once:\n\n```bash\npnpm --dir frontend exec playwright install\n```\n\nThen keep `pnpm stack:dev` running in another terminal and run:\n\n```bash\npnpm check:full\n```\n\nThe full check adds frontend E2E and OpenAPI checks, MongoDB-backed backend tests, a Release build, and backend formatting. Backend tests use the Aspire-managed MongoDB instance on `localhost:27017`.\n\n<details>\n<summary>More development commands</summary>\n\n```bash\npnpm backend:restore\npnpm backend:build\npnpm backend:build:release\npnpm backend:test\npnpm backend:format:check\n\npnpm frontend:dev\npnpm frontend:check\npnpm frontend:lint\npnpm frontend:format:check\npnpm frontend:test:unit\npnpm frontend:test:e2e\npnpm frontend:build\npnpm frontend:api:check\n```\n\n`pnpm frontend:dev` runs only the frontend. It is not an alternative full-stack orchestrator.\n\n</details>\n\n<details>\n<summary>Refresh generated OpenAPI types</summary>\n\nWith the stack running, copy the Identity and Profile HTTP endpoints from the Aspire dashboard:\n\n```bash\ncd frontend\nexport IDENTITY_OPENAPI_URL='<identity-http-url>/openapi/v1.json'\nexport PROFILE_OPENAPI_URL='<profile-http-url>/openapi/v1.json'\n\npnpm api:refresh\npnpm api:generate\npnpm api:check\n```\n\nSnapshots remove runtime-specific `servers` entries so Aspire's dynamic ports do not create noisy diffs. Commit the snapshots and generated declarations together after intentional API changes.\n\n</details>\n\n## Repository tour\n\n```text\nHelpDesk/\n|-- frontend/                 SvelteKit BFF and browser experience\n|-- backend/\n|   |-- AppHost/              Aspire local orchestrator\n|   |-- Common/               Event storage-provider implementation and generic helpers\n|   |-- Contracts/            Cross-service event language\n|   |-- Services/             Identity, Profile, Notifications\n|   `-- Deployment/           Production process launcher\n|-- compose.yaml              Single-VPS production topology\n|-- DEPLOYMENT.md             Deployment and operations guide\n|-- HelpDesk.slnx\n`-- package.json              Monorepo command surface\n```\n\nThe most useful architecture entry points are:\n\n- `backend/Contracts/*` for event contracts and subscriber IDs;\n- `backend/Services/*/Program.cs` for hosts, event hubs, and subscriptions;\n- `backend/Services/*/Subscriptions/` for reactions to remote events;\n- `backend/AppHost/Program.cs` for the local resource graph;\n- `frontend/src/lib/server/api/` for the BFF boundary.\n\n## Production deployment\n\nThe included deployment is a pragmatic single-VPS topology with automatic HTTPS, private backend services, persistent MongoDB and profile-picture volumes, and optional SMTP. Email-driven onboarding is operational in Production only when SMTP is configured and enabled.\n\n```bash\nscripts/deploy-init.sh helpdesk.example.com\n# Configure optional SMTP values in the generated .env\nscripts/deploy.sh\n```\n\nPodman-only hosts can install the included systemd unit after a successful deployment:\n\n```bash\nscripts/install-host-service.sh\n```\n\nSee [DEPLOYMENT.md](DEPLOYMENT.md) for provisioning, secrets, firewall guidance, HTTPS, upgrades, rollback, and host restart behavior.\n\n## The idea worth taking away\n\nThe interesting part of HelpDesk is not simply that it removed a broker. It keeps the constraints that make event-driven services understandable: explicit ownership, facts after local commit, thin contracts, local subscriber effects, and no hidden synchronous RPC chains. Infrastructure can grow when requirements demand it.\n\n## Further reading\n\n| Resource                                                   | Purpose                                                             |\n|------------------------------------------------------------|---------------------------------------------------------------------|\n| [DEPLOYMENT.md](DEPLOYMENT.md)                             | Single-VPS deployment, secrets, HTTPS, and operations               |\n| [`.okf/`](.okf/)                                           | Compact architecture, workflow, security, and operational knowledge |\n| [`backend/Contracts/`](backend/Contracts/)                 | Cross-service event contracts                                       |\n| [`backend/Services/`](backend/Services/)                   | Service implementations and subscription handlers                   |\n| [`backend/AppHost/Program.cs`](backend/AppHost/Program.cs) | Aspire development resource graph                                   |",
  "bytes": 20226,
  "sha": "1f446d226708ef393606593c713fff821580450a2d6216f74432df7797bc1c54",
  "repo_slug": "fastendpoints/helpdesk",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_fastendpoints_helpdesk_okf_index_md_7802db0e/readme"
}