profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

main default branch 418 files Expires Sep 13, 2026, 9:06 AM
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