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

SplitApp — Modulaarne Monoliit (Phase 3)

1. Üldine pilt

                    ┌────────────────────────────────────────────────────┐
                    │            SplitApp.WebApp (Composition Root)      │
                    │   Program.cs · MVC Controllers · Areas/Admin       │
                    │   Areas/Identity · Views · ViewModels              │
                    │   Application/{Services, DTO, Mappers, Persistence}│
                    │   (lifted phase-2 BLL — kasutab IAppUnitOfWork)    │
                    └────────────────────────────────────────────────────┘
                            │            │            │
                            ▼            ▼            ▼
                  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
                  │   Users     │  │    Trips    │  │  Expenses   │
                  │             │  │             │  │             │
                  │  Domain     │  │  Domain     │  │  Domain     │
                  │  Application│  │  Application│  │  Application│
                  │  Infra      │  │  Infra      │  │  Infra      │
                  │  Api        │  │  Api        │  │  Api        │
                  │             │  │             │  │             │
                  │ schema:     │  │  schema:    │  │  schema:    │
                  │  users      │  │   trips     │  │  expenses   │
                  └─────────────┘  └─────────────┘  └─────────────┘
                       │  ▲             │  ▲             │  ▲
                       │  │ MediatR     │  │ MediatR     │  │ MediatR
                       └──┴─────────────┴──┴─────────────┴──┘
                            ┌─────────────────────────┐
                            │  Shared.Contracts       │
                            │   IRequest / INotification│
                            │   (Queries + Events)    │
                            └─────────────────────────┘
                            ┌─────────────────────────┐
                            │  Shared.Kernel          │
                            │  BaseEntity · LangStr   │
                            │  IdentityHelpers (JWT)  │
                            └─────────────────────────┘

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.


2. Lahenduse struktuur (SplitApp.Modular/)

SplitApp.sln
├── src/
│   ├── SplitApp.WebApp/                                 ← composition root, host, MVC + admin
│   │   ├── Program.cs                                   ← DI wiring, AddXxxModule(...)
│   │   ├── Application/                                 ← Phase 2 BLL liigutatud siia
│   │   │   ├── Services/  (+ Admin/, Identity/)         ← TripService, ExpenseService, ...
│   │   │   ├── DTO/                                     ← TripBllDto, ExpenseBllDto, ...
│   │   │   ├── Mappers/                                 ← Domain↔BllDto factory mapperid
│   │   │   └── Persistence/                             ← AppUnitOfWork (3 mooduli DbContexti agregaator)
│   │   ├── Areas/Admin/                                 ← admin UX, ViewModels, eraldi layout
│   │   ├── Areas/Identity/                              ← Razor Pages (Register)
│   │   ├── Controllers/                                 ← klient-MVC kontrollerid
│   │   └── Views/                                       ← klient-vaated
│   ├── Shared/
│   │   ├── SplitApp.Shared.Kernel/                      ← BaseEntity, IBaseRepository, IUnitOfWork, LangStr, IdentityHelpers
│   │   └── SplitApp.Shared.Contracts/                   ← MediatR IRequest / INotification
│   └── Modules/
│       ├── Users/
│       │   ├── SplitApp.Modules.Users.Domain/           ← AppUser, AppRole, AppRefreshToken
│       │   ├── SplitApp.Modules.Users.Application/      ← IIdentityService, MediatR handlerid
│       │   ├── SplitApp.Modules.Users.Infrastructure/   ← UsersDbContext, repod, AddUsersModule
│       │   └── SplitApp.Modules.Users.Api/              ← /api/v1/identity/...
│       ├── Trips/                                        ← sama 4-projekti struktuur, schema "trips"
│       └── Expenses/                                     ← sama 4-projekti struktuur, schema "expenses"
└── tests/
    ├── SplitApp.Modules.Users.Tests/
    ├── SplitApp.Modules.Trips.Tests/
    ├── SplitApp.Modules.Expenses.Tests/
    └── SplitApp.WebApp.IntegrationTests/                ← architecture invariants + smoke

3. Viidete reeglid (compiler-enforced + arch-test verified)

Allikas Lubatud sihtmärgid Märkused
Modules/X/<Layer> (Application/Infrastructure/Api) sama mooduli teised projektid + Shared.Kernel + Shared.Contracts Mitte teise mooduli projektidele
Shared.* mitte ühelegi moodulile
WebApp kõik kolm moodulit (Api + Infrastructure) + Shared composition root

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 [NotMapped] lõiku.

Architecture-testid jälgivad neid invariante (tests/SplitApp.WebApp.IntegrationTests/Architecture/):

  • ModuleBoundaryTests — ükski mooduli Application/Infrastructure/Api ei viita teisele moodulile
  • DbContextSchemaIsolationTests — iga DbContext sisaldab DbSet-e ainult oma mooduli entiteetidele
  • CrossModuleNavigationTests — cross-module nav-property on lubatud ainult [NotMapped]-iga
  • HostBootSmokeTestsWebApplicationFactory<Program> käivitab täis-host'i

4. Moodulite-vahene suhtlus (MediatR)

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.

Saadetakse hetkel:

Leping Omanik Otstarve
GetUserByIdQuery : IRequest<UserDto?> Users Trips/Expenses kasutavad nime kuvamiseks
GetUsersByIdsQuery : IRequest<IReadOnlyList<UserDto>> Users Partii-päring
UserDeletedEvent : INotification Users Trips + Expenses tellivad — eemaldavad seotud kirjed
GetTripByIdQuery : IRequest<TripSummaryDto?> Trips Cross-module reisi-otsing
GetTripParticipantsQuery : IRequest<IReadOnlyList<TripParticipantDto>> Trips
IsTripParticipantQuery : IRequest<bool> Trips IDOR-i kaitse Expenses controller-is
TripDeletedEvent : INotification Trips Expenses tellib — kustutab kulud + arveldused
GetTripExpenseTotalsQuery : IRequest<TripExpenseTotalsDto> Expenses
GetBudgetCategorySpentQuery : IRequest<...> Expenses Eelarvekategooria kulutused
SettlementPlanCompletedEvent : INotification Expenses Trips tellib — märgib reisi "Settled" kui kõik makstud

5. Andmeisolatsioon

Iga moodul omab oma DbContext-i ja Postgres schema:

  • UsersDbContext : IdentityDbContext<AppUser, AppRole, Guid> → schema users
  • TripsDbContext : DbContext → schema trips
  • ExpensesDbContext : DbContext → schema expenses

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.

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:

  • Eel-MediatR valideerimispäringutega (nt IsTripParticipantQuery enne expense split-i salvestamist)
  • Domeeni-sündmustega kustutamisel (UserDeletedEvent, TripDeletedEvent)

6. Sõltuvuste graaf (mooduli sees — Clean Architecture)

Iga moodul on iseseisev mini-Clean-Architecture:

        Shared.Kernel (BaseEntity, contracts)
              ▲
              │
       Module.Domain ◄─── (entiteedid, enumid)
              ▲
       ┌──────┴──────┐
       │             │
  Module.Application  │
              ▲       │
              │       │
       Module.Infrastructure (DbContext, repod, EF migrations)
              ▲
              │
        Module.Api (REST controllers, DTO-d)
              ▲
              │
        WebApp (composition root)

Iga mooduli Infrastructure registreerib enda AddXxxModule(IConfiguration) extension-meetodi. WebApp/Program.cs kutsub kõik kolm:

builder.Services.AddUsersModule(builder.Configuration);
builder.Services.AddTripsModule(builder.Configuration);
builder.Services.AddExpensesModule(builder.Configuration);

7. Phase 3 ↔ Phase 2 vastendus

Phase 2 projekt (kustutatud) Phase 3 sihtmärk
Base.Domain, Base.Contracts Shared.Kernel
Base.Helpers (IdentityHelpers) Shared.Kernel.Auth
App.Domain.Identity.* Modules/Users/SplitApp.Modules.Users.Domain/Entities/
App.Domain.{Trip, TripParticipant, ...} Modules/Trips/SplitApp.Modules.Trips.Domain/Entities/
App.Domain.{Expense, SettlementPlan, ..., Currency} Modules/Expenses/SplitApp.Modules.Expenses.Domain/Entities/
App.DAL.EF.AppDbContext jagatud 3-ks: UsersDbContext, TripsDbContext, ExpensesDbContext
App.BLL.Services.Identity.* Modules/Users/.../Application/Services/
App.BLL.Services.* (Trip, Expense, Settlement, ...) WebApp/Application/Services/ (composition-root facade kõigi 3 mooduli UoW peal)
App.BLL.{DTO, Mappers} WebApp/Application/{DTO, Mappers}
App.Resources/Domain/*.resx (üks ühtne) WebApp/Resources/Views/Shared.{resx,et.resx}
WebApp.ApiControllers.Identity.* Modules/Users/.../Api/Controllers/
WebApp.ApiControllers.{Trips, ...} Modules/Trips/.../Api/Controllers/
WebApp.ApiControllers.{Expenses, Currencies, Settlements, SplitPresets} Modules/Expenses/.../Api/Controllers/
WebApp/{Controllers, Areas/Admin, Areas/Identity, Views} SplitApp.WebApp/{Controllers, Areas/Admin, Areas/Identity, Views} (struktuur sama; namespace re-rooted SplitApp.WebApp.*)

8. Käivitamine

Tootmine (deployd): https://travel.rasmusj.com/

Lokaalselt:

# Repo juurest
docker compose up --build

Tõuseb üles:

  • phase3http://localhost:90 (host port 90 → container port 8080)
  • phase3-db (PostgreSQL 16) — ainult Docker sisevõrgus, host port pole avatud

Iga mooduli migratsioonid jooksevad automaatselt host-i käivitamisel (vt *ModuleExtensions.UseXxxModule()).

# Testid (25 testi 4 projektis)
cd SplitApp.Modular
dotnet test

9. Miks modulaarne monoliit

Lähenemine Probleem
Klassikaline monoliit "Kõik viitab kõigele" — üks muudatus → kaskaad-mõju mujal
Mikroteenused Hajusüsteemide põrgu — võrk, serialiseerimine, eventual consistency
Modulaarne monoliit Selged piirid (nagu mikroteenustel) + lihtne deployment (nagu monoliidil)

Vaata pikemat juttu kursuse modularmonolith.md failist või SplitApp.Modular/docs/ARCHITECTURE.md-ist.


Kokkuvõte

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.