Clean Architecture organizes dependency direction around application and business rules; Vertical Slice Architecture organizes change around individual use cases. They address different design concerns and can be combined. For a large ASP.NET Core application, choose explicit boundaries and testing responsibilities rather than judging architecture from project or folder counts.
Key takeaways
- Dependency rules and feature organization are separate decisions.
- Vertical slices do not require microservices, separate databases or a particular mediator library.
- A layered solution can still have feature-focused use cases.
- Shared business invariants need deliberate ownership in either style.
Clean architecture vs vertical slice: clarify the axis of organization
Microsoft’s web architecture guidance describes Clean Architecture with application logic at the center and infrastructure implementations depending on core abstractions. The aim is to keep important rules from depending directly on implementation details.
Jimmy Bogard’s Vertical Slice Architecture explanation organizes related concerns around a request or use case and seeks to reduce coupling between slices. It also highlights the importance of refactoring judgment as business complexity emerges.
Those ideas do not prescribe one mandatory repository layout. A team can group use cases by feature while protecting shared domain rules behind clear interfaces. It can also create many projects without achieving meaningful dependency control.
For this comparison, a slice means an owned use case such as submitting an order or viewing its status. A core rule means an invariant that must hold across relevant operations, such as preventing an invalid state transition. Neither should be confused with the number of deployed services.
Two architecture diagrams for an ASP.NET Core application
Clean Architecture: dependency direction
API adapter → application use cases → domain rules
Infrastructure adapter → application-owned contracts
Composition root wires concrete implementations.
Runtime requests can call outward through those contracts;
source dependencies keep infrastructure details outside the core.The diagram shows source dependency direction, not a requirement to put every request through a fixed sequence of generic classes. Keep the application contract useful to its consumers. An interface that merely repeats an underlying library API may add maintenance without protecting a meaningful boundary.
Vertical Slice Architecture: feature ownership
ASP.NET Core host
├─ SubmitOrder: request → validation → handler → persistence
├─ CancelOrder: request → authorization → handler → persistence
└─ GetOrder: request → authorization → query → response
Shared where justified: identity policy, transaction conventions,
stable domain invariants and infrastructure configuration.The diagram groups code by the change it serves. It does not permit bypassing security, data integrity or observability standards. Direct data access within a slice is a design choice to evaluate, not a license for unrelated features to change every table.
Compare the workload and change pattern
| Concern | Clean-oriented design | Slice-oriented design | Failure mode to watch |
|---|---|---|---|
| Primary boundary | Core policy separated from adapters | Use case kept locally understandable | Folder names mistaken for enforcement |
| Feature change | May cross several architectural areas | Often concentrated in an owned slice | Shared dependencies still changed unexpectedly |
| Complex business rules | Explicit domain and application ownership | Extract stable invariants as complexity requires | Rules duplicated inconsistently |
| Data access | Adapters behind useful contracts | Tailored access per use case where appropriate | Generic abstractions or uncontrolled schema coupling |
| Testing | Core tests plus adapter and end-to-end checks | Use-case tests plus shared policy checks | Mocks or happy paths hiding real behavior |
| Team autonomy | Clear core and adapter ownership needed | Feature ownership is visible | Shared releases and databases mistaken for independence |
Evaluate the application’s dominant changes. If several channels use the same complex rules, protecting those rules may deserve a strong central boundary. If many use cases are operationally distinct and change independently, grouping their code can simplify navigation and ownership.
Do not assume the more complicated option is more enterprise-ready. Additional interfaces, mapping layers and projects need a reason. Equally, removing every abstraction can make policy reuse and external integration harder to control.
Worked example: changing order cancellation
Consider a fictional order-management application. A new cancellation rule allows a request only before dispatch, requires permission for the tenant and records a reason. An external refund action may occur after the local decision. This is a design exercise, not measured delivery evidence.
In a clean-oriented implementation, an API adapter maps the request to a cancellation use case. The use case checks authorization and invokes the order’s transition rules. Infrastructure implements persistence and external integration contracts. A transaction boundary and an owned delivery mechanism coordinate local state and subsequent work.
In a slice-oriented implementation, the cancellation request, validation, handler and response live together. The slice still uses the authoritative order-transition rule rather than inventing a second version. It follows the same authorization and durable-integration requirements.
The meaningful question is how many unrelated responsibilities must change when cancellation changes. A clean design with one use-case class can be focused; a slice with a giant handler can be difficult to maintain. Inspect the code’s actual coupling before choosing a label.
For the external refund, neither style makes a database transaction and a remote action atomic automatically. Define retries, duplicate handling and reconciliation independently of the folder structure. The architecture must describe failure behavior, not only the successful call sequence.
Testing, coupling and team autonomy
Test the business rule with relevant state transitions, including denied transitions. Test tenant authorization at the application boundary. Test persistence and query behavior against a representative database where provider behavior matters. Test integration failure without creating real financial actions.
Mocks can isolate a policy, but a suite that only verifies method calls may miss a broken query or transaction. Integration tests can reveal those defects, but a slow all-system suite makes every feature expensive to validate. Choose test boundaries from the risk being checked.
Use architecture checks for rules the team intends to enforce, such as preventing core code from referencing infrastructure or stopping one feature from importing another feature’s internal types. Do not write restrictions that the team immediately works around through a shared utilities folder.
Feature ownership helps teams navigate work, but shared schemas, deployment pipelines and release approvals can still constrain autonomy. Record who approves changes to cross-cutting authentication, migrations and shared domain behavior.
Keep duplication decisions explicit. Repeating a small request-specific projection can be reasonable; copying a critical authorization rule is a different risk. Extract shared concepts after identifying a stable responsibility and owner, rather than creating a global abstraction for every similar line.
A selection matrix for a large application
| Observed need | Design response | Proof to request |
|---|---|---|
| Shared complex invariants across channels | Protect a well-owned domain/application boundary | Rules tested independently of delivery adapters |
| Frequent independent feature changes | Group use cases into clear slices | A feature can be understood without touring unrelated folders |
| Many generic pass-through layers | Simplify abstractions that protect no real policy | Reduced indirection without losing tests or controls |
| Handlers repeatedly duplicate business rules | Extract the stable invariant with explicit ownership | Consistent behavior across affected use cases |
| Legacy application with limited test coverage | Improve one bounded feature at a time | Contract preservation and reversible migration |
A practical hybrid is feature-focused application use cases within modules, protected domain rules and explicit infrastructure boundaries. Keep the design small enough for the team to explain and maintain.
For an existing estate, start with one representative feature containing business rules, data access and an integration. Compare navigation, test coverage, change impact and operational behavior. These are proposed evaluation criteria, not claims that one style reduces development time by a fixed percentage.
Avoid a repository-wide rewrite solely to adopt a fashionable pattern. Establish the pain being removed, preserve contracts and migrate incrementally. Coordinate runtime changes through the .NET upgrade planning guide and release changes through the controlled rollout guide.
Common questions
Which architecture style fits a large ASP.NET Core app?
Choose from business complexity, change patterns and ownership. Large applications can use feature slices while enforcing domain and infrastructure boundaries.
Can Clean Architecture and vertical slices be combined?
Yes. Feature organization and dependency direction are separate concerns. Define the shared rules and permitted dependencies explicitly.
Does Vertical Slice Architecture require a mediator library?
No. A library can help dispatch requests, but the architectural choice is how use cases and responsibilities are organized.
Are vertical slices the same as microservices?
No. Slices can live in one application and deployment. Separate service boundaries introduce additional operational and data decisions.
Should every feature have its own repository abstraction?
No universal rule applies. Add a contract when it protects a useful policy or dependency boundary, and test the real data behavior where necessary.
Choose architecture from the changes your team makes
Bring a representative feature, shared rules and current maintenance pain. Discuss your .NET modernization roadmap to evaluate boundaries and an incremental architecture improvement plan.
