architecture.md
7,969 bytes
| 1 | # Clean / Onion Architecture — Project Layer Mapping |
|---|---|
| 2 | |
| 3 | This document maps the 9 csproj projects in [SplitApp/](SplitApp/) to the Onion architecture rings as taught in the TalTech lecture (`courses.taltech.akaver.com/web-applications-with-csharp/lectures/architecture1`). |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## The 9 projects |
| 8 | |
| 9 | ``` |
| 10 | SplitApp/ |
| 11 | ├── Base.Contracts/ ← Domain Contracts (generic / cross-cutting, innermost) |
| 12 | ├── Base.Domain/ ← Domain Primitives (cross-cutting) |
| 13 | ├── Base.Helpers/ ← Infrastructure (cross-cutting helpers) |
| 14 | ├── App.Domain/ ← Domain Layer (CORE) |
| 15 | ├── App.DAL.EF/ ← Infrastructure (Data Access Layer) |
| 16 | ├── App.BLL/ ← Application Services / Business Logic Layer |
| 17 | ├── App.DTO/ ← Presentation Contracts (public API DTOs) |
| 18 | ├── App.Resources/ ← Infrastructure (i18n / localization) |
| 19 | └── WebApp/ ← Presentation / UI (MVC + REST + Composition Root) |
| 20 | ``` |
| 21 | |
| 22 | --- |
| 23 | |
| 24 | ## Onion Rings (innermost → outermost) |
| 25 | |
| 26 | ### Ring 1 — Domain Model (CORE) |
| 27 | |
| 28 | The innermost ring. Knows nothing about EF, ASP.NET, JWT, or any other technology. |
| 29 | |
| 30 | | Project | Lecture name | Contents | |
| 31 | |---|---|---| |
| 32 | | **`Base.Contracts`** | **Domain Contracts (generic / base)** | [IBaseEntity.cs](SplitApp/Base.Contracts/IBaseEntity.cs), [IBaseRepository.cs](SplitApp/Base.Contracts/IBaseRepository.cs), [IUnitOfWork.cs](SplitApp/Base.Contracts/IUnitOfWork.cs) | |
| 33 | | **`Base.Domain`** | **Domain Primitives / base entity classes** | [BaseEntity.cs](SplitApp/Base.Domain/BaseEntity.cs) (Id, CreatedAt, UpdatedAt), [LangStr.cs](SplitApp/Base.Domain/LangStr.cs) (i18n value object) | |
| 34 | | **`App.Domain`** | **Domain Entities + Domain Contracts (app-specific)** | All concrete entities ([Trip.cs](SplitApp/App.Domain/Trip.cs), Expense, BudgetCategory, Currency, TripPoll, ...), enums (`ETripStatus`, `EParticipantRole`, ...), `Identity/` (AppUser, AppRole, AppRefreshToken), and `App.Domain/Contracts/` with `IAppUnitOfWork`, `ITripRepository`, `IExpenseRepository`, etc. | |
| 35 | |
| 36 | **Key rule:** Repository interfaces (`ITripRepository`, `IAppUnitOfWork`) live in `App.Domain/Contracts/`, **not** in the DAL. That is the classical Onion rule — the inner ring defines the contract, the outer ring implements it, so dependencies always point inward. |
| 37 | |
| 38 | --- |
| 39 | |
| 40 | ### Ring 2 — Application / BLL (Business Logic Layer) |
| 41 | |
| 42 | Orchestrates use cases. Talks to the Domain only through the repository contracts in `App.Domain/Contracts/`. |
| 43 | |
| 44 | | Project | Lecture name | Contents | |
| 45 | |---|---|---| |
| 46 | | **`App.BLL`** | **Application Services / Business Logic Layer (BLL)** | `Services/` (`ITripService` + `TripService`, `IExpenseService`, `IInvitationService`, ...), `Services/Admin/` (admin services), `Services/Identity/` (`IIdentityService` — JWT issuing, refresh tokens), `DTO/` (`TripBllDto`, `ExpenseBllDto`, `BalanceBllDto`, ...), `Mappers/` (`TripBllDtoFactory.Create(entity)` and `.ToEntity(dto)`) | |
| 47 | |
| 48 | --- |
| 49 | |
| 50 | ### Ring 3 — Infrastructure |
| 51 | |
| 52 | Implements the contracts from the inner rings using concrete technologies (EF Core, JWT library, .NET resx). |
| 53 | |
| 54 | | Project | Lecture name | Contents | |
| 55 | |---|---|---| |
| 56 | | **`App.DAL.EF`** | **Infrastructure — Data Access Layer (EF Core implementation)** | [AppDbContext.cs](SplitApp/App.DAL.EF/AppDbContext.cs) (EF DbContext, IdentityDbContext), [AppUnitOfWork.cs](SplitApp/App.DAL.EF/AppUnitOfWork.cs) (implements `IAppUnitOfWork`), `Repositories/*` (concrete repos implementing the interfaces from `App.Domain/Contracts/`), `Migrations/`, `Seeding/`, [ServiceCollectionExtensions.cs](SplitApp/App.DAL.EF/ServiceCollectionExtensions.cs) (`AddDalServices`), `UtcDateTimeConverter.cs` | |
| 57 | | **`App.Resources`** | **Infrastructure — i18n / Localization resources** | `Domain/*.resx` + `*.et.resx`, `Common.resx`, `Views/Shared.resx`, `Domain/Enums.resx` | |
| 58 | | **`Base.Helpers`** | **Infrastructure — cross-cutting helpers** | [IdentityHelpers.cs](SplitApp/Base.Helpers/IdentityHelpers.cs) (JWT generate/validate using `System.IdentityModel.Tokens.Jwt`) | |
| 59 | |
| 60 | **Note on DAL contracts placement:** Akaver sometimes splits this into `App.DAL.Contracts` (interfaces) + `App.DAL.EF` (implementation). In this project the DAL contracts are inlined into `App.Domain/Contracts/`, which is also valid Onion — and arguably purer, because the contracts live in the Domain ring rather than in a separate "DAL contracts" project. |
| 61 | |
| 62 | --- |
| 63 | |
| 64 | ### Ring 4 — Presentation / UI (outermost) |
| 65 | |
| 66 | The composition root. Wires everything together. |
| 67 | |
| 68 | | Project | Lecture name | Contents | |
| 69 | |---|---|---| |
| 70 | | **`App.DTO`** | **Presentation Contracts (public API DTOs)** | `v1/TripDto.cs`, `v1/TripCreateDto.cs`, `v1/TripUpdateDto.cs`, `v1/Identity/*` (LoginInfo, RegisterInfo, TokenRefreshInfo) | |
| 71 | | **`WebApp`** | **Presentation / UI** (MVC + REST API + Composition Root) | [Program.cs](SplitApp/WebApp/Program.cs) (composition root), `ApiControllers/` (versioned REST), `Controllers/` (client MVC), `Areas/Admin/Controllers/` + `Areas/Admin/Views/` (admin MVC area), `Areas/Identity/` (scaffolded Identity UI), `Views/`, [ConfigureSwaggerOptions.cs](SplitApp/WebApp/ConfigureSwaggerOptions.cs), `Helpers/` | |
| 72 | |
| 73 | --- |
| 74 | |
| 75 | ## Dependency direction (must flow inward only) |
| 76 | |
| 77 | ``` |
| 78 | WebApp ──────┐ |
| 79 | ├──► App.BLL ──► App.Domain ──► Base.Domain ──► Base.Contracts |
| 80 | App.DTO ─────┘ ▲ |
| 81 | │ (App.Domain/Contracts/ITripRepository etc.) |
| 82 | App.DAL.EF ─────────────────────┘ implements those contracts |
| 83 | App.Resources, Base.Helpers ─── leaf utilities, used by outer rings |
| 84 | ``` |
| 85 | |
| 86 | **Verified in code:** `App.DAL.EF` references `App.Domain` (so it can implement `ITripRepository`), but `App.Domain` does **not** reference `App.DAL.EF`. That is correct. |
| 87 | |
| 88 | --- |
| 89 | |
| 90 | ## What "Infrastructure" means in this project |
| 91 | |
| 92 | In the lecture's vocabulary, **Infrastructure = anything that talks to the outside world or to a specific technology**. In this project that is: |
| 93 | |
| 94 | 1. **`App.DAL.EF`** — primary infrastructure (EF Core, Postgres, migrations). |
| 95 | 2. **`App.Resources`** — secondary infrastructure (resx files for the .NET localization framework). |
| 96 | 3. **`Base.Helpers`** — JWT token plumbing (depends on `System.IdentityModel.Tokens.Jwt`, which is a tech-specific library). |
| 97 | |
| 98 | Everything in those three projects can be swapped (e.g. switch from EF to Dapper, from resx to a translations DB, from JWT to OAuth) without touching `App.Domain` or `App.BLL`. That is the test of "is it infrastructure?". |
| 99 | |
| 100 | --- |
| 101 | |
| 102 | ## Three-tier DTO flow |
| 103 | |
| 104 | ``` |
| 105 | [ DB row ] |
| 106 | │ EF Core |
| 107 | ▼ |
| 108 | App.Domain.Trip ◄── domain entity (innermost) |
| 109 | │ TripBllDtoFactory.Create() in App.BLL/Mappers/ |
| 110 | ▼ |
| 111 | App.BLL.DTO.TripBllDto ◄── BLL-internal DTO |
| 112 | │ inline mapping in WebApp/ApiControllers/TripsController.cs |
| 113 | ▼ |
| 114 | App.DTO.v1.TripDto ◄── public REST DTO (the wire format) |
| 115 | │ JSON |
| 116 | ▼ |
| 117 | [ HTTP response ] |
| 118 | ``` |
| 119 | |
| 120 | - **Entity ↔ BllDto** mapper: [App.BLL/Mappers/TripBllDtoFactory.cs](SplitApp/App.BLL/Mappers/TripBllDtoFactory.cs) |
| 121 | - **BllDto ↔ public ApiDto** mapper: currently inlined in the API controllers (no `WebApp/Mappers/` folder exists yet). |
| 122 | |
| 123 | --- |
| 124 | |
| 125 | ## Summary table — at a glance |
| 126 | |
| 127 | | Onion ring | Lecture name | Project(s) | |
| 128 | |---|---|---| |
| 129 | | 1 (innermost) | **Domain Contracts (generic)** | `Base.Contracts` | |
| 130 | | 1 | **Domain Primitives** | `Base.Domain` | |
| 131 | | 1 | **Domain Entities + app-specific Domain Contracts** | `App.Domain` (incl. `App.Domain/Contracts/`) | |
| 132 | | 2 | **Application Services / BLL** | `App.BLL` | |
| 133 | | 3 | **Infrastructure — DAL** | `App.DAL.EF` | |
| 134 | | 3 | **Infrastructure — i18n** | `App.Resources` | |
| 135 | | 3 | **Infrastructure — cross-cutting helpers** | `Base.Helpers` | |
| 136 | | 4 | **Presentation Contracts (public DTOs)** | `App.DTO` | |
| 137 | | 4 (outermost) | **Presentation / UI + Composition Root** | `WebApp` | |
| 138 | |