Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
YouTube Security VideosVisual Studio Code: VS Code Learn: Extending Agents(24.09.2026 um 21:00 Uhr)
•
YouTube Security VideosGoogle Cloud Tech: Turn Audio into Action with Gemini 3.5 Transcribe(24.09.2026 um 21:00 Uhr)
••••
Unix & Linux ServerUSN-8815-1: libass vulnerabilities(24.09.2026 um 16:57 Uhr)
•••••
YouTube Security VideosVisual Studio Code: VS Code Learn: Extending Agents(24.09.2026 um 21:00 Uhr)
•
YouTube Security VideosGoogle Cloud Tech: Turn Audio into Action with Gemini 3.5 Transcribe(24.09.2026 um 21:00 Uhr)
••••
Unix & Linux ServerUSN-8815-1: libass vulnerabilities(24.09.2026 um 16:57 Uhr)
•••••
Intelligence View
⚡ tsecurity.de Intelligence

How to Design a Maintainable .NET Solution Structure for Growing Teams

Look, I've seen some things. I've opened solutions with 47 projects, each containing exactly one class (I am being dramatic!). I've navigated folder hierarchies so deep that Visual Studio needed a compass. I've traced project references…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Look, I've seen some things. I've opened solutions with 47 projects, each containing exactly one class (I am being dramatic!). I've navigated folder hierarchies so deep that Visual Studio needed a compass. I've traced project references that formed dependency graphs resembling spaghetti thrown at a wall.



And every time, someone said: "We'll refactor it later."



Spoiler: they didn't.






The Problem With "It Works On My Machine" Architecture



Here's what typically happens. You start a project. It's small. Everything lives in one project because why not? Then features grow. You add folders. More folders. Folders inside folders.



Six months later:




MyApp/
├── Controllers/
├── Services/
├── Repositories/
├── Models/
├── ViewModels/
├── DTOs/
├── Helpers/
├── Utilities/
├── Extensions/
├── Common/
├── Shared/
├── Core/
├── Infrastructure/
└── Misc/ ← This is where dreams go to die






Everything depends on everything. Your "Repository" calls your "Helper" which calls your "Service" which calls your "Repository" again. You've built a circular reference, but the compiler doesn't complain because it's all one project.



Then a new developer joins. They ask: "Where should I put this new feature?"



You stare blankly. "Uh... probably Services? Or maybe Helpers? What does it do exactly?"






The Folder Structure That Actually Scales



After years of trial and error (mostly error), here's what works:




src/
├── MyApp.Domain/ # Zero dependencies. Just your business logic.
├── MyApp.Application/ # Use cases. References Domain only.
├── MyApp.Infrastructure/ # External concerns. Databases, APIs, files.
├── MyApp.Api/ # Your web host. Thin. Composition root.
└── MyApp.Shared.Kernel/ # Cross-cutting primitives (optional)

tests/
├── MyApp.Domain.Tests/
├── MyApp.Application.Tests/
├── MyApp.Infrastructure.Tests/
└── MyApp.Api.Tests/






Five projects. Clear boundaries. Dependencies flow one direction: inward.



"But that's just Clean Architecture!"



Yes. And there's a reason everyone keeps reinventing it. It works.






The Dependency Rule (Or: Why Your Architecture Falls Apart)



Here's the one rule that matters:



Dependencies point inward. Always.




Api → Application → Domain
Infrastructure → Application → Domain






Domain knows nothing about databases. Application knows nothing about HTTP. Infrastructure implements the interfaces that Application defines.



Break this rule once, and you've broken it forever. I've watched teams add "just one small reference" from Domain to Infrastructure. Three months later, your business logic imports System.Data.SqlClient.



The compiler won't save you here. You need discipline. Or a tool like NetArchTest to enforce it:




[Fact]
public void Domain_Should_Not_Reference_Infrastructure()
{
var result = Types.InAssembly(typeof(Order).Assembly)
.ShouldNot()
.HaveReferenceTo("MyApp.Infrastructure")
.GetResult();

result.IsSuccessful.Should().BeTrue();
}






Write this test on day one. Thank me later.






Inside the Projects: Folder Conventions That Don't Suck






Domain Project






MyApp.Domain/
├── Entities/
│ ├── Order.cs
│ └── Customer.cs
├── ValueObjects/
│ ├── Money.cs
│ └── Address.cs
├── Enums/
│ └── OrderStatus.cs
├── Events/
│ └── OrderPlacedEvent.cs
├── Exceptions/
│ └── InsufficientStockException.cs
└── Interfaces/
└── IOrderRepository.cs # Just the interface. No implementation.






Notice what's not here: no Services folder. Domain services exist, but they're rare. If you're creating domain services for every entity, you're probably doing anemic domain modeling.






Application Project






MyApp.Application/
├── Features/
│ ├── Orders/
│ │ ├── Commands/
│ │ │ ├── PlaceOrder/
│ │ │ │ ├── PlaceOrderCommand.cs
│ │ │ │ ├── PlaceOrderHandler.cs
│ │ │ │ └── PlaceOrderValidator.cs
│ │ │ └── CancelOrder/
│ │ │ └── ...
│ │ └── Queries/
│ │ └── GetOrderById/
│ │ ├── GetOrderByIdQuery.cs
│ │ ├── GetOrderByIdHandler.cs
│ │ └── OrderDto.cs
│ └── Customers/
│ └── ...
├── Common/
│ ├── Behaviors/
│ │ ├── ValidationBehavior.cs
│ │ └── LoggingBehavior.cs
│ └── Interfaces/
│ └── IApplicationDbContext.cs
└── DependencyInjection.cs






This is "Vertical Slice" meets "CQRS lite." Each feature is self-contained. Want to understand how placing an order works? Look in one folder.



The Misc folder from earlier? It doesn't exist because there's nowhere for random code to hide.






Infrastructure Project






MyApp.Infrastructure/
├── Persistence/
│ ├── ApplicationDbContext.cs
│ ├── Configurations/
│ │ └── OrderConfiguration.cs
│ └── Repositories/
│ └── OrderRepository.cs
├── Services/
│ ├── EmailService.cs
│ └── PaymentGateway.cs
├── Identity/
│ └── IdentityService.cs
└── DependencyInjection.cs






Everything that touches the outside world lives here. Database? Here. Email? Here. Third-party APIs? Here.



When you need to swap your payment provider, you change one folder. The rest of your application doesn't care.






Naming Conventions (The Stuff Nobody Agrees On)



After watching teams argue about this for years, here's what I've landed on:






Projects






{Company}.{Product}.{Layer}






Example: Contoso.Ordering.Domain



Don't get clever. Contoso.Ordering.SuperCore.Base.Abstractions.V2 helps nobody.






Folders





  • Plural for collections: Entities/, Services/, Handlers/


  • Singular for features: Orders/, Customers/ (each is a single feature area)






Files





  • Suffix with role: OrderService.cs, OrderRepository.cs, OrderController.cs


  • Commands/Queries get full names: PlaceOrderCommand.cs, GetOrderByIdQuery.cs



The suffix thing is controversial. Some folks hate IOrderRepository. I've tried dropping the I prefix. It's worse. Your IDE's autocomplete becomes useless.






Interfaces






// In Domain or Application (whoever defines the contract)
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(OrderId id, CancellationToken ct);
Task AddAsync(Order order, CancellationToken ct);
}

// In Infrastructure (the implementation)
public class OrderRepository : IOrderRepository
{
// EF Core, Dapper, whatever
}






Interface lives with the code that uses it, not the code that implements it. This is Dependency Inversion 101, but I still see teams putting IOrderRepository in the Infrastructure project.






Project References: The Hidden Dependency Graph



Here's the .csproj setup that enforces the dependency rule:



Domain.csproj - References nothing (maybe Shared.Kernel)




<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>






Application.csproj - References Domain only




<ItemGroup>
<ProjectReference Include="..\MyApp.Domain\MyApp.Domain.csproj" />
</ItemGroup>






Infrastructure.csproj - References Application (and transitively Domain)




<ItemGroup>
<ProjectReference Include="..\MyApp.Application\MyApp.Application.csproj" />
</ItemGroup>






Api.csproj - References everything (it's the composition root)




<ItemGroup>
<ProjectReference Include="..\MyApp.Application\MyApp.Application.csproj" />
<ProjectReference Include="..\MyApp.Infrastructure\MyApp.Infrastructure.csproj" />
</ItemGroup>






The Api project wires everything together in Program.cs:




builder.Services
.AddApplication() // From Application's DependencyInjection.cs
.AddInfrastructure(); // From Infrastructure's DependencyInjection.cs









The "Shared" Project Debate



Every team eventually wants a "Shared" or "Common" project. Usually for:




  • Extension methods

  • Base classes

  • Cross-cutting attributes

  • Result types



My advice: resist as long as possible. When you can't resist anymore:




MyApp.Shared.Kernel/
├── Primitives/
│ ├── Entity.cs
│ ├── ValueObject.cs
│ └── DomainEvent.cs
├── Results/
│ └── Result.cs
└── Extensions/
└── StringExtensions.cs






Keep it small. The moment this project becomes a dumping ground, you've lost.



Rule of thumb: if you can't explain why something is in Shared in one sentence, it doesn't belong there.






When to Split Into Multiple Solutions



"Should we have one solution or multiple?"



Here's my heuristic:




























Team Size Deployment Recommendation
1-5 devs Monolith One solution
5-15 devs Monolith One solution, strict boundaries
15+ devs Services Multiple solutions, shared packages


Multiple solutions introduce real pain: package versioning, CI/CD complexity, integration testing overhead. Don't pay that cost until you have to.



When you do split:




solutions/
├── ordering/
│ └── Contoso.Ordering.sln
├── inventory/
│ └── Contoso.Inventory.sln
└── shared/
└── Contoso.Shared.sln # Published as NuGet packages






The Shared packages become internal NuGet packages. Each team owns their solution. Integration happens through APIs or messages, not project references.






Common Mistakes I Keep Seeing






1. The "Everything Is A Service" Pattern






// No. Stop.
public class OrderService
{
public Order GetOrder(int id) { ... }
public void PlaceOrder(Order order) { ... }
public void CancelOrder(int id) { ... }
public void UpdateOrder(Order order) { ... }
public decimal CalculateTotal(Order order) { ... }
public bool ValidateOrder(Order order) { ... }
public void SendOrderEmail(Order order) { ... }
// 47 more methods
}






This is a God Class wearing a "Service" disguise. Break it up by use case.






2. Circular Project References (Fixed With a "Shared" Project)



If you need a Shared project just to break circular dependencies, your architecture is wrong. Step back and redraw the boundaries.






3. "Infrastructure" As a Junk Drawer






Infrastructure/
├── Database stuff
├── Email stuff
├── PDF generation
├── Image processing
├── Rate limiting
├── Background jobs
├── Feature flags
├── Analytics
└── That one weird integration with the legacy system nobody understands






When Infrastructure gets too big, split it:




MyApp.Infrastructure.Persistence/
MyApp.Infrastructure.Email/
MyApp.Infrastructure.BackgroundJobs/









4. Putting DTOs in the Wrong Layer



DTOs that leave your API boundary? They go in Application or a dedicated Contracts project.



DTOs for database mapping? They're not DTOs, they're entities. Put them in Domain.



DTOs shared between services? Publish them as a NuGet package from a Contracts project.






The Checklist



Before committing, ask yourself:




  • [ ] Can a new dev find where to add a feature in under 2 minutes?

  • [ ] Does each project have a single, clear responsibility?

  • [ ] Do dependencies flow one direction (inward)?

  • [ ] Is there exactly one place for each type of code?

  • [ ] Can you explain the structure to a junior dev in 5 minutes?



If any answer is "no," you've got work to do.






Getting Started With an Existing Mess



Can't start fresh? Here's the incremental approach:





  1. Add architecture tests - Start enforcing rules before adding more violations


  2. Extract Domain - Pull out entities and business rules first


  3. Define interfaces - Create boundaries with interfaces before moving implementations


  4. Extract Infrastructure - Move external dependencies behind those interfaces


  5. Reorganize into features - Convert folder-by-type into folder-by-feature



Don't try to refactor everything at once. I've seen that movie. It ends with a half-migrated codebase and a demoralized team.






Final Thoughts



Good structure isn't about following a specific template. It's about making the easy path the correct path.



When adding new code is obvious, developers make good decisions. When it's confusing, they make expedient decisions. And expedient decisions compound into architectural debt.



The goal isn't to have the "perfect" structure on day one. It's to have a structure that guides good decisions as the team grows.



Start simple. Enforce boundaries. Refactor when pain demands it.



Now if you'll excuse me, I have a legacy solution with 73 projects to untangle. Wish me luck.






About me



I'm a Systems Architect who is passionate about distributed systems, .NET clean code, logging, performance, and production debugging.



🧑‍💻 Check out my projects on GitHub: mashrulhaque



👉 Follow me here on dev.to for more .NET posts

IoC Intelligence (1 Indikatoren)
dev[.]to
CTI Threat Relationship Graph3 Knoten / 2 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - How to Design a Maintainable .NET Solution Structure for Growing Teams
id: c965852e-57bc-4419-9265-c704aba61bbc
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      DestinationHostname:
        - 'dev.to'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "How to Design a Maintainable ." ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
(dest_host="dev.to")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
destination.domain: ("dev.to") and event.category: "network"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where DestinationHostName in ("dev.to")
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich How to Design a Maintainable .NET Soluti.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How to Design a Maintainable .NET Solution Structure for Growing Teams

Thematisch verwandte Begriffe: Design, Maintainable, Solution, Structure · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61823 | code16 Sharp is a Laravel-based framework for building content-managemen…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel • Rechts: nächster Artikel • unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...
↗ Original-Quelle