arhitektuur.md
13,366 bytes
| 1 | # SplitApp — Modulaarne Monoliit (Phase 3) |
|---|---|
| 2 | |
| 3 | ## 1. Üldine pilt |
| 4 | |
| 5 | ``` |
| 6 | ┌────────────────────────────────────────────────────┐ |
| 7 | │ SplitApp.WebApp (Composition Root) │ |
| 8 | │ Program.cs · MVC Controllers · Areas/Admin │ |
| 9 | │ Areas/Identity · Views · ViewModels │ |
| 10 | │ Application/{Services, DTO, Mappers, Persistence}│ |
| 11 | │ (lifted phase-2 BLL — kasutab IAppUnitOfWork) │ |
| 12 | └────────────────────────────────────────────────────┘ |
| 13 | │ │ │ |
| 14 | ▼ ▼ ▼ |
| 15 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ |
| 16 | │ Users │ │ Trips │ │ Expenses │ |
| 17 | │ │ │ │ │ │ |
| 18 | │ Domain │ │ Domain │ │ Domain │ |
| 19 | │ Application│ │ Application│ │ Application│ |
| 20 | │ Infra │ │ Infra │ │ Infra │ |
| 21 | │ Api │ │ Api │ │ Api │ |
| 22 | │ │ │ │ │ │ |
| 23 | │ schema: │ │ schema: │ │ schema: │ |
| 24 | │ users │ │ trips │ │ expenses │ |
| 25 | └─────────────┘ └─────────────┘ └─────────────┘ |
| 26 | │ ▲ │ ▲ │ ▲ |
| 27 | │ │ MediatR │ │ MediatR │ │ MediatR |
| 28 | └──┴─────────────┴──┴─────────────┴──┘ |
| 29 | ┌─────────────────────────┐ |
| 30 | │ Shared.Contracts │ |
| 31 | │ IRequest / INotification│ |
| 32 | │ (Queries + Events) │ |
| 33 | └─────────────────────────┘ |
| 34 | ┌─────────────────────────┐ |
| 35 | │ Shared.Kernel │ |
| 36 | │ BaseEntity · LangStr │ |
| 37 | │ IdentityHelpers (JWT) │ |
| 38 | └─────────────────────────┘ |
| 39 | ``` |
| 40 | |
| 41 | **Põhimõte:** üks deployment, kolm sisemiselt isoleeritud moodulit. Iga moodul omab oma domeeni, andmeid (Postgres schema) ja teenuseid. Moodulid suhtlevad **ainult MediatR-i kaudu** — mitte ühtegi otseviidet teise mooduli sisemusele. |
| 42 | |
| 43 | --- |
| 44 | |
| 45 | ## 2. Lahenduse struktuur (`SplitApp.Modular/`) |
| 46 | |
| 47 | ``` |
| 48 | SplitApp.sln |
| 49 | ├── src/ |
| 50 | │ ├── SplitApp.WebApp/ ← composition root, host, MVC + admin |
| 51 | │ │ ├── Program.cs ← DI wiring, AddXxxModule(...) |
| 52 | │ │ ├── Application/ ← Phase 2 BLL liigutatud siia |
| 53 | │ │ │ ├── Services/ (+ Admin/, Identity/) ← TripService, ExpenseService, ... |
| 54 | │ │ │ ├── DTO/ ← TripBllDto, ExpenseBllDto, ... |
| 55 | │ │ │ ├── Mappers/ ← Domain↔BllDto factory mapperid |
| 56 | │ │ │ └── Persistence/ ← AppUnitOfWork (3 mooduli DbContexti agregaator) |
| 57 | │ │ ├── Areas/Admin/ ← admin UX, ViewModels, eraldi layout |
| 58 | │ │ ├── Areas/Identity/ ← Razor Pages (Register) |
| 59 | │ │ ├── Controllers/ ← klient-MVC kontrollerid |
| 60 | │ │ └── Views/ ← klient-vaated |
| 61 | │ ├── Shared/ |
| 62 | │ │ ├── SplitApp.Shared.Kernel/ ← BaseEntity, IBaseRepository, IUnitOfWork, LangStr, IdentityHelpers |
| 63 | │ │ └── SplitApp.Shared.Contracts/ ← MediatR IRequest / INotification |
| 64 | │ └── Modules/ |
| 65 | │ ├── Users/ |
| 66 | │ │ ├── SplitApp.Modules.Users.Domain/ ← AppUser, AppRole, AppRefreshToken |
| 67 | │ │ ├── SplitApp.Modules.Users.Application/ ← IIdentityService, MediatR handlerid |
| 68 | │ │ ├── SplitApp.Modules.Users.Infrastructure/ ← UsersDbContext, repod, AddUsersModule |
| 69 | │ │ └── SplitApp.Modules.Users.Api/ ← /api/v1/identity/... |
| 70 | │ ├── Trips/ ← sama 4-projekti struktuur, schema "trips" |
| 71 | │ └── Expenses/ ← sama 4-projekti struktuur, schema "expenses" |
| 72 | └── tests/ |
| 73 | ├── SplitApp.Modules.Users.Tests/ |
| 74 | ├── SplitApp.Modules.Trips.Tests/ |
| 75 | ├── SplitApp.Modules.Expenses.Tests/ |
| 76 | └── SplitApp.WebApp.IntegrationTests/ ← architecture invariants + smoke |
| 77 | ``` |
| 78 | |
| 79 | --- |
| 80 | |
| 81 | ## 3. Viidete reeglid (compiler-enforced + arch-test verified) |
| 82 | |
| 83 | | Allikas | Lubatud sihtmärgid | Märkused | |
| 84 | |---------|-------------------|----------| |
| 85 | | `Modules/X/<Layer>` (Application/Infrastructure/Api) | sama mooduli teised projektid + `Shared.Kernel` + `Shared.Contracts` | **Mitte teise mooduli projektidele** | |
| 86 | | `Shared.*` | mitte ühelegi moodulile | | |
| 87 | | `WebApp` | kõik kolm moodulit (Api + Infrastructure) + Shared | composition root | |
| 88 | |
| 89 | **Erand Domain tasandil:** Phase 2 vaate-renderdamise paarsuse hoidmiseks (`Trip.CreatedBy.Email`, `Expense.PaidByUser.FirstName` jne) on entiteedid säilitanud cross-module nav-property'd, kuid annotatsiooniga `[NotMapped]`, et EF kunagi ei ületaks Postgres schema piiri. Vaata [SplitApp.Modular/docs/ARCHITECTURE.md](SplitApp.Modular/docs/ARCHITECTURE.md) `[NotMapped]` lõiku. |
| 90 | |
| 91 | Architecture-testid jälgivad neid invariante (`tests/SplitApp.WebApp.IntegrationTests/Architecture/`): |
| 92 | - `ModuleBoundaryTests` — ükski mooduli `Application`/`Infrastructure`/`Api` ei viita teisele moodulile |
| 93 | - `DbContextSchemaIsolationTests` — iga DbContext sisaldab DbSet-e ainult oma mooduli entiteetidele |
| 94 | - `CrossModuleNavigationTests` — cross-module nav-property on lubatud ainult `[NotMapped]`-iga |
| 95 | - `HostBootSmokeTests` — `WebApplicationFactory<Program>` käivitab täis-host'i |
| 96 | |
| 97 | --- |
| 98 | |
| 99 | ## 4. Moodulite-vahene suhtlus (MediatR) |
| 100 | |
| 101 | Kõik kõned üle mooduli piiri lähevad läbi MediatR. Lepingud (`IRequest<T>` või `INotification`) elavad `Shared.Contracts/<Moodul>/{Queries|Events|Commands}/`. Handlerid omanik-mooduli `Application` või `Infrastructure` kihis. |
| 102 | |
| 103 | Saadetakse hetkel: |
| 104 | |
| 105 | | Leping | Omanik | Otstarve | |
| 106 | |--------|--------|----------| |
| 107 | | `GetUserByIdQuery : IRequest<UserDto?>` | Users | Trips/Expenses kasutavad nime kuvamiseks | |
| 108 | | `GetUsersByIdsQuery : IRequest<IReadOnlyList<UserDto>>` | Users | Partii-päring | |
| 109 | | `UserDeletedEvent : INotification` | Users | Trips + Expenses tellivad — eemaldavad seotud kirjed | |
| 110 | | `GetTripByIdQuery : IRequest<TripSummaryDto?>` | Trips | Cross-module reisi-otsing | |
| 111 | | `GetTripParticipantsQuery : IRequest<IReadOnlyList<TripParticipantDto>>` | Trips | | |
| 112 | | `IsTripParticipantQuery : IRequest<bool>` | Trips | IDOR-i kaitse Expenses controller-is | |
| 113 | | `TripDeletedEvent : INotification` | Trips | Expenses tellib — kustutab kulud + arveldused | |
| 114 | | `GetTripExpenseTotalsQuery : IRequest<TripExpenseTotalsDto>` | Expenses | | |
| 115 | | `GetBudgetCategorySpentQuery : IRequest<...>` | Expenses | Eelarvekategooria kulutused | |
| 116 | | `SettlementPlanCompletedEvent : INotification` | Expenses | Trips tellib — märgib reisi "Settled" kui kõik makstud | |
| 117 | |
| 118 | --- |
| 119 | |
| 120 | ## 5. Andmeisolatsioon |
| 121 | |
| 122 | Iga moodul omab oma `DbContext`-i ja Postgres schema: |
| 123 | |
| 124 | - `UsersDbContext : IdentityDbContext<AppUser, AppRole, Guid>` → schema `users` |
| 125 | - `TripsDbContext : DbContext` → schema `trips` |
| 126 | - `ExpensesDbContext : DbContext` → schema `expenses` |
| 127 | |
| 128 | Kõik kolm ühenduvad **samasse** Postgres andmebaasi (sama `ConnectionStrings:DefaultConnection`). Schemad — mitte eraldi DB-d — annavad isolatsiooni. **Cross-module SQL JOIN-id on keelatud**; cross-module andmed komponeerib WebApp facade läbi MediatR ja `CrossModuleNavigationLoader`-i. |
| 129 | |
| 130 | Cross-module entiteedi-viited on lihtsad `Guid` väljad **ilma** EF foreign-key piiranguta (nt `Trip.CreatedById : Guid` viitab kontseptuaalselt `users.AspNetUsers.Id`-le, aga mitte `FOREIGN KEY` kaudu). Andmete terviklikkus tagatakse: |
| 131 | - Eel-MediatR valideerimispäringutega (nt `IsTripParticipantQuery` enne expense split-i salvestamist) |
| 132 | - Domeeni-sündmustega kustutamisel (`UserDeletedEvent`, `TripDeletedEvent`) |
| 133 | |
| 134 | --- |
| 135 | |
| 136 | ## 6. Sõltuvuste graaf (mooduli sees — Clean Architecture) |
| 137 | |
| 138 | Iga moodul on iseseisev mini-Clean-Architecture: |
| 139 | |
| 140 | ``` |
| 141 | Shared.Kernel (BaseEntity, contracts) |
| 142 | ▲ |
| 143 | │ |
| 144 | Module.Domain ◄─── (entiteedid, enumid) |
| 145 | ▲ |
| 146 | ┌──────┴──────┐ |
| 147 | │ │ |
| 148 | Module.Application │ |
| 149 | ▲ │ |
| 150 | │ │ |
| 151 | Module.Infrastructure (DbContext, repod, EF migrations) |
| 152 | ▲ |
| 153 | │ |
| 154 | Module.Api (REST controllers, DTO-d) |
| 155 | ▲ |
| 156 | │ |
| 157 | WebApp (composition root) |
| 158 | ``` |
| 159 | |
| 160 | Iga mooduli `Infrastructure` registreerib enda `AddXxxModule(IConfiguration)` extension-meetodi. `WebApp/Program.cs` kutsub kõik kolm: |
| 161 | |
| 162 | ```csharp |
| 163 | builder.Services.AddUsersModule(builder.Configuration); |
| 164 | builder.Services.AddTripsModule(builder.Configuration); |
| 165 | builder.Services.AddExpensesModule(builder.Configuration); |
| 166 | ``` |
| 167 | |
| 168 | --- |
| 169 | |
| 170 | ## 7. Phase 3 ↔ Phase 2 vastendus |
| 171 | |
| 172 | | Phase 2 projekt (kustutatud) | Phase 3 sihtmärk | |
| 173 | |---|---| |
| 174 | | `Base.Domain`, `Base.Contracts` | `Shared.Kernel` | |
| 175 | | `Base.Helpers` (IdentityHelpers) | `Shared.Kernel.Auth` | |
| 176 | | `App.Domain.Identity.*` | `Modules/Users/SplitApp.Modules.Users.Domain/Entities/` | |
| 177 | | `App.Domain.{Trip, TripParticipant, ...}` | `Modules/Trips/SplitApp.Modules.Trips.Domain/Entities/` | |
| 178 | | `App.Domain.{Expense, SettlementPlan, ..., Currency}` | `Modules/Expenses/SplitApp.Modules.Expenses.Domain/Entities/` | |
| 179 | | `App.DAL.EF.AppDbContext` | jagatud 3-ks: `UsersDbContext`, `TripsDbContext`, `ExpensesDbContext` | |
| 180 | | `App.BLL.Services.Identity.*` | `Modules/Users/.../Application/Services/` | |
| 181 | | `App.BLL.Services.*` (Trip, Expense, Settlement, ...) | `WebApp/Application/Services/` (composition-root facade kõigi 3 mooduli UoW peal) | |
| 182 | | `App.BLL.{DTO, Mappers}` | `WebApp/Application/{DTO, Mappers}` | |
| 183 | | `App.Resources/Domain/*.resx` | (üks ühtne) `WebApp/Resources/Views/Shared.{resx,et.resx}` | |
| 184 | | `WebApp.ApiControllers.Identity.*` | `Modules/Users/.../Api/Controllers/` | |
| 185 | | `WebApp.ApiControllers.{Trips, ...}` | `Modules/Trips/.../Api/Controllers/` | |
| 186 | | `WebApp.ApiControllers.{Expenses, Currencies, Settlements, SplitPresets}` | `Modules/Expenses/.../Api/Controllers/` | |
| 187 | | `WebApp/{Controllers, Areas/Admin, Areas/Identity, Views}` | `SplitApp.WebApp/{Controllers, Areas/Admin, Areas/Identity, Views}` (struktuur sama; namespace re-rooted `SplitApp.WebApp.*`) | |
| 188 | |
| 189 | --- |
| 190 | |
| 191 | ## 8. Käivitamine |
| 192 | |
| 193 | **Tootmine (deployd):** https://travel.rasmusj.com/ |
| 194 | |
| 195 | **Lokaalselt:** |
| 196 | |
| 197 | ```bash |
| 198 | # Repo juurest |
| 199 | docker compose up --build |
| 200 | ``` |
| 201 | |
| 202 | Tõuseb üles: |
| 203 | - `phase3` → http://localhost:90 (host port `90` → container port `8080`) |
| 204 | - `phase3-db` (PostgreSQL 16) — ainult Docker sisevõrgus, host port pole avatud |
| 205 | |
| 206 | Iga mooduli migratsioonid jooksevad automaatselt host-i käivitamisel (vt `*ModuleExtensions.UseXxxModule()`). |
| 207 | |
| 208 | ```bash |
| 209 | # Testid (25 testi 4 projektis) |
| 210 | cd SplitApp.Modular |
| 211 | dotnet test |
| 212 | ``` |
| 213 | |
| 214 | --- |
| 215 | |
| 216 | ## 9. Miks modulaarne monoliit |
| 217 | |
| 218 | | Lähenemine | Probleem | |
| 219 | |------------|----------| |
| 220 | | Klassikaline monoliit | "Kõik viitab kõigele" — üks muudatus → kaskaad-mõju mujal | |
| 221 | | Mikroteenused | Hajusüsteemide põrgu — võrk, serialiseerimine, eventual consistency | |
| 222 | | **Modulaarne monoliit** | **Selged piirid (nagu mikroteenustel) + lihtne deployment (nagu monoliidil)** | |
| 223 | |
| 224 | Vaata pikemat juttu kursuse [modularmonolith.md](modularmonolith.md) failist või [SplitApp.Modular/docs/ARCHITECTURE.md](SplitApp.Modular/docs/ARCHITECTURE.md)-ist. |
| 225 | |
| 226 | --- |
| 227 | |
| 228 | ## Kokkuvõte |
| 229 | |
| 230 | SplitApp Phase 3 on **modulaarne monoliit** kolme isoleeritud mooduliga (Users, Trips, Expenses). Iga moodul on iseseisev mini-Clean-Architecture oma `Domain`/`Application`/`Infrastructure`/`Api` projektidega ja oma Postgres schema. Cross-module suhtlus käib eranditult **MediatR-i** kaudu (`IRequest`/`INotification`), mitte otsesete `<ProjectReference>`-ite kaudu. Architecture-testid lukustavad need invariandid CI-ajal. |
| 231 | |