Skip to content

Commit 43fa956

Browse files
authored
Update README with project details and instructions
Added project overview, architecture details, tech stack, key features, project structure, API reference, installation instructions, Docker setup, testing, CI/CD, lessons learned, roadmap, and acknowledgements.
1 parent f14aab2 commit 43fa956

1 file changed

Lines changed: 287 additions & 1 deletion

File tree

README.md

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,287 @@
1-
# TaskManagerAPI
1+
# TaskManagerAPI
2+
3+
[![CI](https://github.com/LolghElmo/TaskManagerAPI/actions/workflows/CI.yml/badge.svg)](https://github.com/LolghElmo/TaskManagerAPI/actions/workflows/CI.yml)
4+
![.NET](https://img.shields.io/badge/.NET-10.0-512BD4?logo=dotnet)
5+
![C#](https://img.shields.io/badge/C%23-13-239120?logo=csharp)
6+
![SQL Server](https://img.shields.io/badge/SQL_Server-2022-CC2927?logo=microsoftsqlserver&logoColor=white)
7+
![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=white)
8+
9+
A microservices-based task management API built with **.NET 10**, designed as an exercise in applying real-world backend patterns: Clean Architecture, CQRS, JWT authentication, a reverse-proxy API gateway, containerized SQL Server, and full testing including integration tests with real databases via Testcontainers.
10+
11+
> This is my first end-to-end backend project built to learn, not copied from a template. Everything here (architecture decisions, test setup, CI, Docker wiring) was figured out step by step. The goal was to ship something that mirrors how a production service is actually structured, not a toy CRUD app.
12+
13+
---
14+
15+
## Table of Contents
16+
17+
- [Architecture](#architecture)
18+
- [Tech Stack](#tech-stack)
19+
- [Key Features](#key-features)
20+
- [Project Structure](#project-structure)
21+
- [API Reference](#api-reference)
22+
- [Installation](#installation)
23+
- [Running with Docker](#running-with-docker)
24+
- [Testing](#testing)
25+
- [CI/CD](#cicd)
26+
- [What I Learned](#what-i-learned)
27+
- [Roadmap](#roadmap)
28+
- [Acknowledgements](#acknowledgements)
29+
30+
---
31+
32+
## Architecture
33+
34+
Three independently deployable services sit behind a YARP reverse-proxy gateway. Each service owns its own database and follows Clean Architecture (Domain -> Application -> Infrastructure -> Api).
35+
36+
```mermaid
37+
flowchart TD
38+
Client([Client])
39+
Gateway[Gateway.Api<br/>YARP Reverse Proxy]
40+
Identity[IdentityService.Api<br/>Register / Login<br/>JWT Issuer]
41+
Tasks[TasksService.Api<br/>Todo CRUD<br/>JWT-protected]
42+
DB[(SQL Server<br/>Dockerized)]
43+
44+
Client --> Gateway
45+
Gateway --> Identity
46+
Gateway --> Tasks
47+
Identity --> DB
48+
Tasks --> DB
49+
```
50+
51+
Each service is layered:
52+
53+
- **Domain**: entities, interfaces, no external dependencies
54+
- **Application**: CQRS handlers (MediatR), DTOs, validators, mapping profiles
55+
- **Infrastructure**: EF Core `DbContext`, repositories, JWT provider, external service implementations
56+
- **Api**: controllers, middleware, DI composition root
57+
58+
---
59+
60+
## Tech Stack
61+
62+
| Concern | Choice |
63+
|---|---|
64+
| Runtime | .NET 10 / C# 13 |
65+
| API Gateway | YARP (Yet Another Reverse Proxy) |
66+
| Auth | ASP.NET Core Identity + JWT Bearer |
67+
| CQRS / Mediator | MediatR |
68+
| Validation | FluentValidation |
69+
| ORM | Entity Framework Core 10 |
70+
| Database | SQL Server 2022 (containerized) |
71+
| Mapping | AutoMapper |
72+
| Logging | Serilog |
73+
| API Docs | Scalar (OpenAPI) |
74+
| Unit Tests | xUnit, Moq, FluentAssertions |
75+
| Integration Tests | `WebApplicationFactory`, Testcontainers.MsSql |
76+
| Build Automation | Nuke Build |
77+
| CI | GitHub Actions |
78+
| Containerization | Docker + Docker Compose |
79+
80+
---
81+
82+
## Key Features
83+
84+
- **Clean Architecture**: enforced per service. Domain has zero dependencies; Api depends on Application; Infrastructure plugs in at composition root.
85+
- **CQRS with MediatR**: every use case is a `Command` or `Query` with its own handler. No fat controllers.
86+
- **JWT authentication**: issued by `IdentityService`, consumed by `TasksService` through shared validation parameters.
87+
- **FluentValidation pipeline**: requests validated before reaching handlers via a MediatR behavior.
88+
- **Global exception handling**: using `IExceptionHandler` returning RFC 7807 `ProblemDetails`.
89+
- **Pagination**: on list endpoints (`pageNumber`, `pageSize`) with total count metadata.
90+
- **Result&lt;T&gt; pattern**: handlers return explicit success/failure instead of throwing for control flow.
91+
- **Secrets management**: User Secrets for local dev, `.env` + `docker-compose.override.yml` for Docker; nothing sensitive committed.
92+
- **Fail-fast configuration**: services validate connection strings and JWT keys at startup, not per-request.
93+
- **Integration tests with real SQL Server**: via Testcontainers not InMemory fakes. Tests spin up a disposable SQL Server container per test class.
94+
95+
---
96+
97+
## Project Structure
98+
99+
```
100+
TaskManagerAPI/
101+
├── Gateway.Api/ # YARP reverse proxy
102+
├── Common/ # Shared Result<T> type
103+
104+
├── IdentityService.Api/ # Auth endpoints, DI composition root
105+
├── IdentityService.Application/ # CQRS handlers, DTOs, validators
106+
├── IdentityService.Domain/ # User entity, service interfaces
107+
├── IdentityService.Infrastructure/ # EF Core, ASP.NET Identity, JWT provider
108+
├── IdentityService.UnitTests/
109+
├── IdentityService.IntegrationTests/
110+
111+
├── TasksService.Api/ # Todo endpoints (JWT-protected)
112+
├── TasksService.Application/ # CQRS handlers, pagination, validators
113+
├── TasksService.Domain/ # TodoItem entity, repository interface
114+
├── TasksService.Infrastructure/ # EF Core DbContext, repository impl
115+
├── TasksService.UnitTests/
116+
├── TasksService.IntegrationTests/
117+
118+
├── build/ # Nuke build automation
119+
├── docker-compose.yml
120+
├── docker-compose.override.example.yml
121+
└── .env.example
122+
```
123+
124+
---
125+
126+
## API Reference
127+
128+
Full interactive docs are available at `/scalar/v1` on each service when running locally.
129+
130+
### IdentityService
131+
132+
| Method | Endpoint | Auth | Description |
133+
|---|---|---|---|
134+
| POST | `/api/auth/register` || Create a new user account |
135+
| POST | `/api/auth/login` || Authenticate and receive a JWT |
136+
137+
### TasksService
138+
139+
| Method | Endpoint | Auth | Description |
140+
|---|---|---|---|
141+
| GET | `/api/todos?pageNumber=1&pageSize=10` | JWT | List current user's todos (paginated) |
142+
| GET | `/api/todos/{id}` | JWT | Get a single todo |
143+
| POST | `/api/todos` | JWT | Create a todo |
144+
| PUT | `/api/todos/{id}` | JWT | Update a todo |
145+
| DELETE | `/api/todos/{id}` | JWT | Delete a todo |
146+
147+
All `TasksService` routes are also reachable through the gateway at `http://localhost:5001/tasks/...`.
148+
149+
---
150+
151+
## Installation
152+
153+
### Prerequisites
154+
155+
- [.NET 10 SDK](https://dotnet.microsoft.com/download)
156+
- [Docker Desktop](https://www.docker.com/products/docker-desktop/)
157+
- (Optional) Visual Studio 2022+ or JetBrains Rider
158+
159+
### Clone
160+
161+
```bash
162+
git clone https://github.com/LolghElmo/TaskManagerAPI.git
163+
cd TaskManagerAPI
164+
```
165+
166+
### Configure secrets
167+
168+
Copy the example env file and fill in a password + JWT key:
169+
170+
```bash
171+
cp .env.example .env
172+
cp docker-compose.override.example.yml docker-compose.override.yml
173+
```
174+
175+
Edit `.env`:
176+
177+
```env
178+
SA_PASSWORD=Your_Strong_Password_123!
179+
JWT_SECRET_KEY=a-long-random-string-at-least-32-characters
180+
```
181+
182+
For local (non-Docker) runs, use User Secrets instead:
183+
184+
```bash
185+
cd IdentityService.Api
186+
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=IdentityDb;Trusted_Connection=True;TrustServerCertificate=True"
187+
dotnet user-secrets set "JwtSettings:SecretKey" "a-long-random-string-at-least-32-characters"
188+
```
189+
190+
Repeat for `TasksService.Api` with its own connection string (e.g. `Database=TasksDb`) and the **same** `JwtSettings:SecretKey` so it can validate tokens issued by `IdentityService`.
191+
192+
---
193+
194+
## Running with Docker
195+
196+
Spin up everything (SQL Server + all three services) with one command:
197+
198+
```bash
199+
docker compose up --build
200+
```
201+
202+
Services will be available at:
203+
204+
- Gateway: `http://localhost:5001`
205+
- IdentityService: `http://localhost:5002`
206+
- TasksService: `http://localhost:5003`
207+
208+
The SQL Server container runs a healthcheck, and both API services wait for `service_healthy` before starting — no more race-condition crashes on first boot.
209+
210+
Tear down:
211+
212+
```bash
213+
docker compose down # keeps the volume
214+
docker compose down -v # wipes the database volume
215+
```
216+
217+
---
218+
219+
## Testing
220+
221+
```bash
222+
dotnet test
223+
```
224+
225+
The suite currently runs **20 tests** across 4 projects (15 unit, 5 integration), split into two styles:
226+
227+
- **Unit tests** (`*.UnitTests`): handlers tested in isolation with Moq'd repositories, mappers, and loggers. Fast, no I/O.
228+
- **Integration tests** (`*.IntegrationTests`): spin up a real SQL Server container via [Testcontainers](https://dotnet.testcontainers.org/), boot the actual API through `WebApplicationFactory<Program>`, and hit endpoints over HTTP. Catches the bugs unit tests can't: EF migrations, JSON serialization, auth middleware, DI wiring.
229+
230+
Integration tests set environment variables (`ConnectionStrings__DefaultConnection`, `JwtSettings__SecretKey`) in `IAsyncLifetime.InitializeAsync` **before** the host is built — this is the key to making `WebApplicationFactory` work with Testcontainers.
231+
232+
---
233+
234+
## CI/CD
235+
236+
Continuous integration runs on every push via GitHub Actions. The workflow is generated from a typed [Nuke](https://nuke.build/) build using the `[GitHubActions]` attribute with no hand-written YAML.
237+
238+
Run the same pipeline locally:
239+
240+
```bash
241+
./build.sh # or build.cmd / build.ps1
242+
```
243+
244+
Targets: `Clean -> Restore -> Compile -> Test`.
245+
246+
---
247+
248+
## What I Learned
249+
250+
A few things this project taught me that I wouldn't have picked up from a tutorial:
251+
252+
- **Testcontainers + `WebApplicationFactory`**: environment variables for connection strings have to be set in `IAsyncLifetime.InitializeAsync` *before* the host builds. `ConfigureAppConfiguration` is too late under minimal hosting.
253+
- **Result<T> over exceptions**: using an explicit result type for expected failures (login failed, todo not found) made handlers easier to test and removed most of the try/catch noise from controllers.
254+
- **YARP route transforms**: without them, the gateway leaks the internal service topology straight to the client. Route prefixes matter.
255+
- **Fail-fast config validation**: checking connection strings and JWT keys with `string.IsNullOrWhiteSpace` at startup surfaces config problems immediately instead of as cryptic 500s on the first request.
256+
- **Nuke generating GitHub Actions**: the `[GitHubActions]` attribute replaces hand-written YAML entirely the same Test target runs locally and on CI, so "works on my machine" is no longer a thing.
257+
- **Docker healthchecks + `depends_on: service_healthy`**: without them, services race SQL Server on first boot and crashloop until it's ready.
258+
259+
---
260+
261+
## Roadmap
262+
263+
Things I'd add next:
264+
265+
- Refresh tokens + token revocation
266+
- Redis-backed distributed cache for `GET /api/todos`
267+
- Health check endpoints (`/health`, `/health/ready`)
268+
- Structured request logging with correlation IDs across services
269+
- RabbitMQ/MassTransit for an async "task assigned" event
270+
- OpenTelemetry + Jaeger/Seq for distributed tracing
271+
- Deploy to Azure Container Apps
272+
273+
---
274+
275+
## Acknowledgements
276+
277+
This project was built as my first full-stack backend. I leaned on these resources to learn the patterns:
278+
279+
- [Clean Architecture with ASP.NET Core 10](https://www.youtube.com/watch?v=rjefnUC9Z90)
280+
- [How to Implement the CQRS Pattern in Clean Architecture (from scratch)](https://www.youtube.com/watch?v=85YbMEb1qkQ)
281+
- [Intro to MediatR — Implementing CQRS and Mediator Patterns](https://www.youtube.com/watch?v=yozD5Tnd8nw)
282+
- [ASP.NET Core Integration Testing Tutorial](https://www.youtube.com/watch?v=RXSPCIrrjHc)
283+
- [The Best Way To Use Docker For Integration Testing In .NET](https://www.youtube.com/watch?v=tj5ZCtvgXKY)
284+
- Microsoft Docs — ASP.NET Core, EF Core, Identity
285+
- [Testcontainers for .NET](https://dotnet.testcontainers.org/) documentation
286+
- YARP official samples
287+

0 commit comments

Comments
 (0)