phongnguyend/Practical.CleanArchitecture
phongnguyend/Practical.CleanArchitecture has 2,464 GitHub stars and 612 forks. GitHot measured 0 stars in 24 hours, -1 in 7 days, and +1 in 15 days. Last checked 2026-11-08 15:54 UTC. Last push 2026-10-30.
Full-stack .Net 10 Clean Architecture (Microservices, Modular Monolith, Monolith), Blazor, Angular 22, React 19, Vue 3.5, BFF with YARP, NextJs 16, Domain-Driven Design, CQRS, SOLID, Asp.Net Core Identity Custom Storage, OpenID Connect, EF Core, OpenTelemetry, SignalR, Background Services, Health Checks, Rate Limiting, Clouds (Azure, AWS, GCP), ...
Measured star growth
- in 24h
- 0
- in 7 days
- -1
- in 15 days
- +1
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
Original:warning: Warning
The code samples contain multiple ways and patterns to do things and not always be considered best practices or recommended for all situations.
Database Centric vs Domain Centric Architecture

Hexagonal Architecture

Onion Architecture

The Clean Architecture

Classic Three-layer Architecture

Modern Four-layer Architecture

Layer Dependencies

Layer Examples

Testing Pyramid

Vertical Slice Architecture (Modular Monolith)

Solution Structure



How to Run:
Update Configuration
Additional Configuration Sources
Open ClassifiedAds.WebMVC/appsettings.json and jump to ConfigurationSources section.
"ConfigurationSources": { "SqlServer": { "IsEnabled": false, "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SqlQuery": "select [Key], [Value] from ConfigurationEntries" }, "AzureKeyVault": { "IsEnabled": false, "VaultName": "https://xxx.vault.azure.net/" } },Get from Sql Server database:
"ConfigurationSources": { "SqlServer": { "IsEnabled": true, "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SqlQuery": "select [Key], [Value] from ConfigurationEntries" }, },Get from Azure Key Vault:
"ConfigurationSources": { "AzureKeyVault": { "IsEnabled": true, "VaultName": "https://xxx.vault.azure.net/" } },Use Both:
"ConfigurationSources": { "SqlServer": { "IsEnabled": true, "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SqlQuery": "select [Key], [Value] from ConfigurationEntries" }, "AzureKeyVault": { "IsEnabled": true, "VaultName": "https://xxx.vault.azure.net/" } },
Storage
Open ClassifiedAds.WebMVC/appsettings.json, ClassifiedAds.WebAPI/appsettings.json and jump to Storage section.
"Storage": { "Provider": "Local", },Use Local Files:
"Storage": { "Provider": "Local", "Local": { "Path": "E:\\files" }, },Use Azure Blob:
"Storage": { "Provider": "Azure", "Azure": { "ConnectionString": "xxx", "Container": "classifiedadds" }, },Use Amazon S3:
"Storage": { "Provider": "Amazon", "Amazon": { "AccessKeyID": "xxx", "SecretAccessKey": "xxx", "BucketName": "classifiedadds", "RegionEndpoint": "ap-southeast-1" } },
Message Broker
Open below files and jump to Messaging section:
"Messaging": { "Provider": "RabbitMQ", }Use RabbitMQ
"Messaging": { "Provider": "RabbitMQ", "RabbitMQ": { "HostName": "localhost", "UserName": "guest", "Password": "guest", "ExchangeName": "amq.direct", "RoutingKeys": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" }, "QueueNames": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" } } }Use Kafka:
"Messaging": { "Provider": "Kafka", "Kafka": { "BootstrapServers": "localhost:9092", "Topics": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" }, } }Use Azure Queue Storage:
"Messaging": { "Provider": "AzureQueue", "AzureQueue": { "ConnectionString": "xxx", "QueueNames": { "FileUploadedEvent": "classifiedadds-fileuploaded", "FileDeletedEvent": "classifiedadds-filedeleted", "EmailMessageCreatedEvent": "classifiedadds-emailcreated", "SmsMessageCreatedEvent": "classifiedadds-smscreated" } } }Use Azure Service Bus:
"Messaging": { "Provider": "AzureServiceBus", "AzureServiceBus": { "ConnectionString": "xxx", "QueueNames": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" } } }
Logging
- Open and jump to Logging section of below files:
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.Background/appsettings.json
"Logging": { "LogLevel": { "Default": "Warning" }, "File": { "MinimumLogEventLevel": "Information" }, "Elasticsearch": { "IsEnabled": false, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, "EventLog": { "IsEnabled": false, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } }, - Write to Local file (./logs/log.txt). Always enabled.
"Logging": { "File": { "MinimumLogEventLevel": "Information" }, }, - Write to Elasticsearch:
"Logging": { "Elasticsearch": { "IsEnabled": true, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, }, - Write to Windows Event Log (Windows only):
"Logging": { "EventLog": { "IsEnabled": true, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } }, - Enable all options:
"Logging": { "LogLevel": { "Default": "Warning" }, "File": { "MinimumLogEventLevel": "Information" }, "Elasticsearch": { "IsEnabled": true, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, "EventLog": { "IsEnabled": true, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } },
Caching
- Open and jump to Caching section of below files:
"Caching": { "InMemory": { }, "Distributed": { } }, - Configure options for In Memory Cache:
"Caching": { "InMemory": { "SizeLimit": null }, }, - Use In Memory Distributed Cache (For Local Testing):
"Caching": { "Distributed": { "Provider": "InMemory", "InMemory": { "SizeLimit": null } } }, - Use Redis Distributed Cache:
"Caching": { "Distributed": { "Provider": "Redis", "Redis": { "Configuration": "xxx.redis.cache.windows.net:6380,password=xxx,ssl=True,abortConnect=False", "InstanceName": "" } } }, - Use Sql Server Distributed Cache:
dotnet tool install --global dotnet-sql-cache --version="5.0" dotnet sql-cache create "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#" dbo CacheEntries"Caching": { "Distributed": { "Provider": "SqlServer", "SqlServer": { "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SchemaName": "dbo", "TableName": "CacheEntries" } } },
Monitoring
- Open and jump to Monitoring section of below files:
"Monitoring": { }, - Use Azure Application Insights:
"Monitoring": { "AzureApplicationInsights": { "IsEnabled": true, "InstrumentationKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "EnableSqlCommandTextInstrumentation": true } }, - Use OpenTelemetry:
"Monitoring": { "OpenTelemetry": { "IsEnabled": true, "ServiceName": "ClassifiedAds.WebAPI", "TraceEnabled": true, "MetricEnabled": true, "Otlp": { "IsEnabled": false, "Endpoint": "http://localhost:4317" } } }, - Use Both:
"Monitoring": { "AzureApplicationInsights": { "IsEnabled": true, "InstrumentationKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "EnableSqlCommandTextInstrumentation": true }, "OpenTelemetry": { "IsEnabled": true, "ServiceName": "ClassifiedAds.WebAPI", "TraceEnabled": true, "MetricEnabled": true, "Otlp": { "IsEnabled": false, "Endpoint": "http://localhost:4317" } } },
Interceptors
- Open and jump to Interceptors section of below files:
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.Background/appsettings.json
"Interceptors": { "LoggingInterceptor": true, "ErrorCatchingInterceptor": false },
Security Headers
- Open ClassifiedAds.WebAPI/appsettings.json and jump to SecurityHeaders section:
"SecurityHeaders": { "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" }, - Open ClassifiedAds.WebMVC/appsettings.json and jump to SecurityHeaders section:
"SecurityHeaders": { "Content-Security-Policy": "form-action 'self'; frame-ancestors 'none'", "Feature-Policy": "camera 'none'", "Referrer-Policy": "strict-origin-when-cross-origin", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "X-XSS-Protection": "1; mode=block", "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" },
Cross-Origin Resource Sharing (CORS)
- Open ClassifiedAds.WebAPI/appsettings.json and jump to CORS section:
"CORS": { "AllowAnyOrigin": false, "AllowedOrigins": [ "http://localhost:4200", "http://localhost:3000", "http://localhost:8080" ] },
External Login
- Open ClassifiedAds.IdentityServer/appsettings.json and jump to ExternalLogin section:
"ExternalLogin": { "AzureActiveDirectory": { "IsEnabled": true, "Authority": "https://login.microsoftonline.com/<Directory (tenant) ID>", "ClientId": "<Application (client) ID", "ClientSecret": "xxx" }, "Microsoft": { "IsEnabled": true, "ClientId": "<Application (client) ID", "ClientSecret": "xxx" }, "Google": { "IsEnabled": true, "ClientId": "xxx", "ClientSecret": "xxx" }, "Facebook": { "IsEnabled": true, "AppId": "xxx", "AppSecret": "xxx" } },
Sending Email
- Open ClassifiedAds.Background/appsettings.json and jump to Notification -> Email section:
"Notification": { "Email": { "Provider": "Fake", } } - Use SmtpClient:
"Notification": { "Email": { "Provider": "SmtpClient", "SmtpClient": { "Host": "localhost", "Port": "", "UserName": "", "Password": "", "EnableSsl": "" } } }
Sending SMS
- Open ClassifiedAds.Background/appsettings.json and jump to Notification -> Sms section:
"Notification": { "Sms": { "Provider": "Fake", } } - Use Twilio
"Notification": { "Sms": { "Provider": "Twilio", "Twilio": { "AccountSId": "", "AuthToken": "", "FromNumber": "" } } }
Run or Debug the Solution
Web MVC Home Page: https://localhost:44364/

Navigate to Health Checks UI https://localhost:44364/healthchecks-ui#/healthchecks and make sure everything is green.

Login on Identity Server:
- Option 1: Use default created account:
- User Name: [email protected]
- Password: v*7Un8b4rcN@<-RN
- Option 2: Register new account at https://localhost:44367/Account/Register

- Option 1: Use default created account:
Open Blazor Home Page at: https://localhost:44331

How to Build and Run Single Page Applications:
Angular:
Navigate to folder: UIs/angular/
npm install ng serveUpdate environment.ts & environment.prod.ts
export const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.Angular" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:4200/" };Go to http://localhost:4200/

React:
Navigate to folder: UIs/reactjs/
npm install npm run devUpdate environment.dev.tsx & environment.tsx
const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.React" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:3000/" }; export default environment;Go to http://localhost:3000/

Vue:
- Navigate to folder: UIs/vuejs/
npm install npm run dev - Update environment.dev.ts & environment.dev.ts
const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.Vue" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:8080/" }; export default environment;
- Navigate to folder: UIs/vuejs/
Go to http://localhost:8080/

Before Login, go to Identity Server https://localhost:44367/Client to make sure application clients have been registered:

How to Run on Docker Containers:
Add Migrations if you haven't done on previous steps:
- Install dotnet-ef cli:
dotnet tool install --global dotnet-ef --version="8.0" - Navigate to ClassifiedAds.Migrator and run these commands:
dotnet ef migrations add Init --context AdsDbContext -o Migrations/AdsDb
- Install dotnet-ef cli:
Navigate to Monolith and run:
docker compose build docker compose upOpen Web MVC Home Page at: http://host.docker.internal:9003

Navigate to Health Checks UI http://host.docker.internal:9003/healthchecks-ui#/healthchecks and make sure everything is green.

Login on Identity Server:
- Use default created account: [email protected] / v*7Un8b4rcN@<-RN
- Register new account at http://host.docker.internal:9000/Account/Register
Open Blazor Home Page at: http://host.docker.internal:9008

How to Run Integration & End to End Tests:
Update ClassifiedAds.IntegrationTests/appsettings.json
{ "OpenIdConnect": { "Authority": "https://localhost:44367", "ClientId": "ClassifiedAds.WebMVC", "ClientSecret": "secret", "RequireHttpsMetadata": "true" }, "WebAPI": { "Endpoint": "https://localhost:44312" }, "Login": { "UserName": "[email protected]", "Password": "v*7Un8b4rcN@<-RN", "Scope": "ClassifiedAds.WebAPI" } }Download Chrome Driver

Update EndToEndUiTests/appsettings.json
{ "ChromeDriverPath": "D:\\Downloads\\chromedriver_win32\\72", "Login": { "Url": "https://localhost:44364/Home/Login", "UserName": "[email protected]", "Password": "v*7Un8b4rcN@<-RN" } }
Application URLs:
https://github.com/phongnguyend/Practical.CleanArchitecture/wiki/Application-URLs
Roadmap:
https://github.com/phongnguyend/Practical.CleanArchitecture/wiki/Roadmap
Licence 🔑
This repository is licensed under the MIT license.
Duende.IdentityServer License 🔑
Duende.IdentityServer is available under both a FOSS (RPL) and a commercial license.
For the production environment, it is necessary to get a specific license, if you would like more information about the licensing of Duende.IdentityServer - please check this link.
The source code under /src/IdentityServers/Duende folder uses the source code from https://github.com/DuendeSoftware/IdentityServer.Quickstart.UI which is under the terms of the following license.
Explore related projects
freeCodeCamp/freeCodeCamp
freeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.
458,757 starsdonnemartin/system-design-primer
Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards.
377,335 starsreact/react
The library for web and native user interfaces.
252,413 starsvuejs/vue
This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
212,631 stars