profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
README.md 5,607 bytes
1 # SplitApp — Phase 3 Modular Monolith
2
3 ASP.NET Core 10 modular monolith for splitting trip expenses among participants. Three modules (Users, Trips, Expenses) communicating via MediatR; one deployable; per-module Postgres schemas. Full phase-2 UI surface preserved (101 Razor views, 21 MVC + Admin controllers, identity Razor pages, 10 REST API controllers).
4
5 See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for layout, reference rules, contracts, per-module migration commands, and the `[NotMapped]` cross-module navigation caveat.
6
7 ## Run
8
9 ### With Docker (recommended)
10
11 From the repo root (one level up from this directory):
12
13 ```bash
14 docker compose up --build
15 ```
16
17 **Deployment:** runs locally via Docker Compose (see the repo root README).
18
19 Locally, phase 3 listens on **http://localhost:90** (host port `90` → container port `8080`). Each module's migrations are applied automatically on startup. The container is named `phase3`. Postgres uses dedicated schemas `users` / `trips` / `expenses` inside the same `splitapp` database (DB is internal-only — not exposed to host).
20
21 The single `Dockerfile` lives at the repo root and copies from `SplitApp.Modular/`. There is no separate Dockerfile inside this directory — the root one is canonical.
22
23 ### Without Docker
24
25 ```bash
26 docker compose up -d db
27 dotnet run --project src/SplitApp.WebApp
28 ```
29
30 By default `dotnet run` uses `https://localhost:7133` / `http://localhost:5297` (see `Properties/launchSettings.json`).
31
32 ## URL map
33
34 | URL | Purpose |
35 |-----|---------|
36 | `/` | Landing page (MVC) |
37 | `/Trips`, `/Trips/Create`, `/Trips/Details/{id}`, `/Trips/Edit/{id}`, `/Trips/Delete/{id}` | Trip CRUD |
38 | `/Members?tripId={id}` and `/Members/AcceptInvitation/{token}` | Trip participants + invitation flow |
39 | `/Expenses?tripId={id}` (with Create/Edit/Delete) | Trip expenses |
40 | `/Budget?tripId={id}` (with CreateCategory/EditCategory/DeleteCategory) | Budget categories per trip |
41 | `/Settlement?tripId={id}` | Balances + settlement plans |
42 | `/PollsClient?tripId={id}` (Create/Details) | Trip polls |
43 | `/WishlistClient?tripId={id}` (Create/Edit/Delete) | Trip wishlist |
44 | `/Identity/Account/Register` | Cookie-based register |
45 | `/Admin/Dashboard` | Admin home (`admin` role) |
46 | `/Admin/{Users, Trips, Expenses, BudgetCategories, Currencies, Invitations, Polls, SettlementPlans, SettlementPayments, SplitPresets, TripParticipants, Wishlist}` | Admin CRUD over each entity |
47 | `/swagger` | Swagger UI listing every module's REST endpoints |
48
49 ## REST API
50
51 | Module | Endpoints |
52 |--------|-----------|
53 | Users | `/api/v1/identity/account/{register, login, logout, refreshtokendata}` |
54 | Trips | `/api/v1/trips`, `/api/v1/budgetcategories`, `/api/v1/invitations`, `/api/v1/polls`, `/api/v1/wishlist` |
55 | Expenses | `/api/v1/expenses`, `/api/v1/currencies`, `/api/v1/settlements`, `/api/v1/splitpresets` |
56
57 ## Tests
58
59 ```bash
60 dotnet test
61 ```
62
63 **25 tests** across four projects:
64
65 - **Per-module unit tests** — `CurrencyConverter` (5), `LangStr` (7), `IdentityHelpers` JWT round-trips (4)
66 - **Architecture invariants** (in `tests/SplitApp.WebApp.IntegrationTests/Architecture/`):
67 - `ModuleBoundaryTests` — no module's `Application`/`Infrastructure`/`Api` may `<ProjectReference>` another module
68 - `DbContextSchemaIsolationTests` — every `DbContext` exposes `DbSet<T>` only for entities in its own `Domain` project
69 - `CrossModuleNavigationTests` — cross-module navigation properties allowed only when `[NotMapped]`; the WebApp facade hydrates them in-memory after loading from the owning module's DbContext, so EF never crosses schemas
70 - **Smoke** — `WebApplicationFactory<Program>` boots the full host in `Testing` env (skipping migrations) and serves `/`, `/Home/Index`, returns 401 on unauthenticated API hit
71
72 A failing architecture test means a developer just violated the modular-monolith invariant.
73
74 ## Module owners
75
76 | Module | Owns | Schema |
77 |--------|------|--------|
78 | Users | Identity, JWT issuance, refresh tokens, user profile | `users` |
79 | Trips | Trips, participants, invitations, polls, wishlists, budget categories | `trips` |
80 | Expenses | Expenses, splits, settlement plans, settlement payments, split presets, currencies | `expenses` |
81
82 ## Caveat — `[NotMapped]` cross-module navigation properties
83
84 To preserve phase 2's view-rendering parity (BLL DTO factories that read `Trip.DefaultCurrency.Code`, `TripParticipant.User.FirstName`, etc.) the entity classes still declare those navigation properties — annotated `[NotMapped]` so EF never crosses schemas. The `CrossModuleNavigationLoader` in `src/SplitApp.WebApp/Application/Persistence/` populates them in C# after entity load by querying the appropriate module's DbContext.
85
86 Keeping the property *types* on the entities required adding `<ProjectReference>` between Domain projects:
87
88 ```
89 Modules/Trips/SplitApp.Modules.Trips.Domain
90 → Modules/Users/SplitApp.Modules.Users.Domain (for AppUser refs)
91 → Modules/Expenses/SplitApp.Modules.Expenses.Domain (for Currency refs)
92 Modules/Expenses/SplitApp.Modules.Expenses.Domain
93 → Modules/Users/SplitApp.Modules.Users.Domain (for AppUser refs)
94 ```
95
96 This bends the strict "no direct references between modules" rule from `phase3.md` at the **Domain** level. **`Application`, `Infrastructure`, and `Api` projects remain isolated** — they never `<ProjectReference>` another module — and continue to use MediatR for actual function calls. Schema isolation, MediatR-only inter-module communication, and per-module DbContext ownership are all preserved at runtime; only the entity *type system* is shared.
97