Your project follows Clean Architecture principles correctly with proper dependency flow and layer separation. The implementation uses modern architectural patterns including CQRS with MediatR, FluentValidation, and the Result pattern for robust, maintainable code.
// ArchitectureTests.csusingNetArchTest.Rules;usingXunit;namespacePetfolio.Service.ArchitectureTests;publicclassArchitectureTests{privateconststringDomainNamespace="Petfolio.Service.Domain";privateconststringApplicationNamespace="Petfolio.Service.Application";privateconststringInfrastructureNamespace="Petfolio.Service.Infrastructure";privateconststringApiNamespace="Petfolio.Service.Api";[Fact]publicvoidDomain_Should_Not_HaveDependencyOnOtherLayers(){// Arrangevarassembly=typeof(Domain.Entities.Animal).Assembly;// Actvarresult=Types.InAssembly(assembly).Should().NotHaveDependencyOn(ApplicationNamespace).And().NotHaveDependencyOn(InfrastructureNamespace).And().NotHaveDependencyOn(ApiNamespace).GetResult();// AssertAssert.True(result.IsSuccessful,$"Domain layer has dependencies on: {string.Join(",", result.FailingTypeNames ?? Array.Empty<string>())}");}[Fact]publicvoidDomain_Should_Not_HaveDependencyOnExternalFrameworks(){// Arrangevarassembly=typeof(Domain.Entities.Animal).Assembly;// Actvarresult=Types.InAssembly(assembly).Should().NotHaveDependencyOn("EntityFrameworkCore").And().NotHaveDependencyOn("Microsoft.AspNetCore").GetResult();// AssertAssert.True(result.IsSuccessful,"Domain layer should not depend on external frameworks");}[Fact]publicvoidApplication_Should_Not_HaveDependencyOnInfrastructure(){// Arrangevarassembly=typeof(Application.Services.AnimalService).Assembly;// Actvarresult=Types.InAssembly(assembly).Should().NotHaveDependencyOn(InfrastructureNamespace).And().NotHaveDependencyOn(ApiNamespace).GetResult();// AssertAssert.True(result.IsSuccessful,$"Application layer has dependencies on: {string.Join(",", result.FailingTypeNames ?? Array.Empty<string>())}");}[Fact]publicvoidApplication_Should_Not_ReferenceDatabaseConcerns(){// Arrangevarassembly=typeof(Application.Services.AnimalService).Assembly;// Actvarresult=Types.InAssembly(assembly).Should().NotHaveDependencyOn("EntityFrameworkCore").GetResult();// AssertAssert.True(result.IsSuccessful,"Application layer should not reference Entity Framework");}[Fact]publicvoidInfrastructure_Should_Not_HaveDependencyOnApplication(){// Arrangevarassembly=typeof(Infrastructure.Repositories.AnimalRepository).Assembly;// Actvarresult=Types.InAssembly(assembly).Should().NotHaveDependencyOn(ApplicationNamespace).And().NotHaveDependencyOn(ApiNamespace).GetResult();// AssertAssert.True(result.IsSuccessful,$"Infrastructure layer has dependencies on: {string.Join(",", result.FailingTypeNames ?? Array.Empty<string>())}");}[Fact]publicvoidControllers_Should_OnlyDependOnApplicationServices(){// Arrangevarassembly=typeof(Api.Controllers.AnimalsController).Assembly;// Actvarresult=Types.InAssembly(assembly).That().ResideInNamespace("Petfolio.Service.Api.Controllers").Should().NotHaveDependencyOn("DbContext").And().NotHaveDependencyOn("Petfolio.Service.Infrastructure.Repositories").GetResult();// AssertAssert.True(result.IsSuccessful,"Controllers should not directly depend on repositories or DbContext");}[Fact]publicvoidRepositories_Should_ImplementDomainInterfaces(){// Arrangevarassembly=typeof(Infrastructure.Repositories.AnimalRepository).Assembly;// Actvarresult=Types.InAssembly(assembly).That().ResideInNamespace("Petfolio.Service.Infrastructure.Repositories").And().HaveNameEndingWith("Repository").Should().ImplementInterface(typeof(Domain.Repositories.IAnimalRepository).GetInterfaces().FirstOrDefault()??typeof(object)).GetResult();// Assert - This is informational, repositories should implement domain interfaces// but we can't test this generically without reflection}[Fact]publicvoidDomain_Entities_Should_InheritFromBaseEntity(){// Arrangevarassembly=typeof(Domain.Entities.Animal).Assembly;// Actvarresult=Types.InAssembly(assembly).That().ResideInNamespace("Petfolio.Service.Domain.Entities").And().AreClasses().And().DoNotHaveName("BaseEntity").Should().Inherit(typeof(Domain.Common.BaseEntity)).GetResult();// AssertAssert.True(result.IsSuccessful,$"Entities not inheriting from BaseEntity: {string.Join(",", result.FailingTypeNames ?? Array.Empty<string>())}");}[Fact]publicvoidValueObjects_Should_BeRecords(){// Arrangevarassembly=typeof(Domain.ValueObjects.Species).Assembly;// Actvartypes=assembly.GetTypes().Where(t=>t.Namespace=="Petfolio.Service.Domain.ValueObjects").ToList();varnonRecords=types.Where(t=>!t.IsValueType&&!t.IsClass).ToList();// Assert - Value objects should typically be records for immutability// This is a guideline rather than strict rule}}
# .editorconfig (add to root)root=true[*.cs]# Enforce naming conventionsdotnet_naming_rule.interfaces_should_be_prefixed_with_i.severity=warningdotnet_naming_rule.interfaces_should_be_prefixed_with_i.symbols=interfacedotnet_naming_rule.interfaces_should_be_prefixed_with_i.style=begins_with_i# Async methods should end with Asyncdotnet_naming_rule.async_methods_end_in_async.severity=warningdotnet_naming_rule.async_methods_end_in_async.symbols=async_methodsdotnet_naming_rule.async_methods_end_in_async.style=ends_with_async# Private fields should be _camelCasedotnet_naming_rule.private_members_with_underscore.symbols=private_fieldsdotnet_naming_rule.private_members_with_underscore.style=prefix_underscoredotnet_naming_rule.private_members_with_underscore.severity=warning
<Project><ImportProject="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))"/><PropertyGroup><!-- Domain should have no external dependencies --><NoWarn>$(NoWarn)</NoWarn></PropertyGroup></Project>
#!/bin/sh."$(dirname"$0")/_/husky.sh"# Run architecture testsdotnettestPetfolio.Service.ArchitectureTests/Petfolio.Service.ArchitectureTests.csproj--no-build--no-restore
# Run build to check for analyzer warningsdotnetbuild--no-restore/p:TreatWarningsAsErrors=true
# validate-architecture.ps1$ErrorActionPreference="Stop"Write-Host"Validating Clean Architecture..."-ForegroundColorCyan# Check Domain has no project references$domainRefs=Select-Xml-Path"Petfolio.Service.Domain/Petfolio.Service.Domain.csproj"-XPath"//ProjectReference"if($domainRefs){Write-Error"Domain layer should have no project references!"exit1}# Check Application only references Domain$appRefs=Select-Xml-Path"Petfolio.Service.Application/Petfolio.Service.Application.csproj"-XPath"//ProjectReference/@Include"$invalidAppRefs=$appRefs|Where-Object{$_.Node.Value-notlike"*Domain.csproj"}if($invalidAppRefs){Write-Error"Application layer should only reference Domain!"exit1}# Check Infrastructure only references Domain$infraRefs=Select-Xml-Path"Petfolio.Service.Infrastructure/Petfolio.Service.Infrastructure.csproj"-XPath"//ProjectReference/@Include"$invalidInfraRefs=$infraRefs|Where-Object{$_.Node.Value-notlike"*Domain.csproj"}if($invalidInfraRefs){Write-Error"Infrastructure layer should only reference Domain!"exit1}Write-Host"Clean Architecture validation passed!"-ForegroundColorGreen
# Contributing Guidelines## Clean Architecture Rules### Never Do This:-Add Infrastructure references to Application layer
-Add DbContext to Application services
-Put business logic in controllers
-Create anemic domain models
-Skip validation in entities
### Always Do This:-Use IUnitOfWork for transaction management
-Put business logic in domain entities
-Use factory methods for entity creation
-Validate in value objects and entities
-Keep controllers thin
### Before Submitting PR:- [ ] Run architecture tests: `dotnet test`- [ ] Check for analyzer warnings: `dotnet build`- [ ] Review dependency graph
- [ ] Update documentation if needed