A comprehensive guide to understanding and choosing between the different architectural patterns provided in Project Templates.
- Introduction
- Layered Architecture
- Clean Architecture
- Vertical Slice Architecture
- Comparison Matrix
- Decision Framework
Software architecture is the fundamental structure of a system. It defines how different components interact, communicate, and depend on each other. Choosing the right architecture is crucial because:
- Scalability - Your system should grow without requiring complete rewrites
- Maintainability - New developers should understand the codebase quickly
- Testability - Components should be testable independently
- Flexibility - Technology changes shouldn't require architectural changes
- Team Productivity - Clear structure enables parallel development
Project Templates offers three proven architectural patterns, each with different tradeoffs. Understanding these patterns will help you make the right choice for your project.
Layered Architecture organizes code into horizontal layers, where each layer has a specific responsibility. This is one of the most common and well-understood architectural patterns.
┌─────────────────────────┐
│ Presentation Layer │ (Controllers, APIs, Views)
├─────────────────────────┤
│ Application Layer │ (Business Logic, Use Cases)
├─────────────────────────┤
│ Domain Layer │ (Core Business Rules, Entities)
├─────────────────────────┤
│ Data Access Layer │ (Repositories, Data Sources)
├─────────────────────────┤
│ Infrastructure Layer │ (Cross-cutting Concerns)
└─────────────────────────┘
- Handles HTTP requests and responses
- Input validation
- Serialization/deserialization
- Rendering responses
- Typically: Controllers, API endpoints, middleware
- Business logic orchestration
- Use case implementation
- Cross-cutting concerns (logging, validation)
- Typically: Services, handlers, validators
- Core business rules and logic
- Entity definitions
- Business exceptions
- Typically: Domain models, value objects, specifications
- Database interactions
- Repository pattern implementation
- Query execution
- Transaction management
- External service integrations
- Caching implementations
- Logging providers
- File system operations
- Email services, etc.
- Upper layers can depend on lower layers
- Lower layers should NOT depend on upper layers
- Each layer should be loosely coupled to others
✅ Simple and Intuitive - Easy to understand for developers of all levels ✅ Clear Separation of Concerns - Each layer has a specific responsibility ✅ Good for Team Collaboration - Teams can work on different layers independently ✅ Familiar Pattern - Most developers have experience with layered architecture ✅ Good for Iterative Development - Fast development cycles ✅ Flexible Technology Choices - Different technologies can be used per layer
❌ Potential for "Fat" Layers - Layers can become bloated with unrelated code ❌ Database-Centric - Often driven by database schema design ❌ Tight Coupling - Layers often become tightly coupled over time ❌ Shared Databases - May encourage sharing database schemas across features ❌ Hard to Scale - Can become monolithic as project grows
Choose Layered if:
- ✅ This is your first production application
- ✅ Your team is mixed in experience level
- ✅ You need to move quickly to market
- ✅ Your project scope is well-defined and bounded
- ✅ You want a simple, easy-to-understand structure
- ✅ Your team is comfortable with traditional architectures
Don't Choose Layered if:
- ❌ Your business logic is highly complex
- ❌ You need strict architectural boundaries
- ❌ Multiple independent teams will work on the project
- ❌ You anticipate significant technical debt
- ❌ Your project will have a long lifespan with many changes
Application/
├── Features/
│ ├── Users/
│ │ ├── CreateUserCommand.cs
│ │ ├── GetUserByIdQuery.cs
│ │ └── UpdateUserCommand.cs
│ └── Products/
│ ├── GetAllProductsQuery.cs
│ └── CreateProductCommand.cs
├── Validators/
├── Behaviors/
└── AutoMapper/
Domain/
├── Entities/
│ ├── User.cs
│ └── Product.cs
├── Interfaces/
├── Exceptions/
└── Specifications/
Infrastructure/
├── Data/
│ ├── ApplicationDbContext.cs
│ └── Migrations/
├── Repositories/
├── Services/
└── ExternalServices/
Api/
├── Controllers/
│ ├── UsersController.cs
│ └── ProductsController.cs
├── Middleware/
├── Filters/
└── Program.cs
Clean Architecture, pioneered by Robert C. Martin ("Uncle Bob"), is a design philosophy that emphasizes independence, testability, and long-term sustainability. The key principle is that dependencies point inward toward the core business logic.
┌────────────────┐
│ Frameworks & │
│ Drivers │ (Web, DB, UI)
└────────┬───────┘
│
┌──────────────┴──────────────┐
│ Interface Adapters │
│ (Controllers, Gateways, │
│ Repositories) │
└──────────────┬──────────────┘
│
┌──────────────┴──────────────┐
│ Application Business │
│ Rules (Use Cases, │
│ Boundaries) │
└──────────────┬──────────────┘
│
┌──────────────┴──────────────┐
│ Enterprise Business │
│ Rules (Entities) │
└────────────────────────────┘
- Enterprise-wide business rules
- Classes shared across the entire system
- Contains the most high-level business logic
- Rarely changes when requirements change
- Application-specific business rules
- Orchestrates flow of data
- Should be independent of external frameworks
- Contains logic specific to the application
- Controllers, presenters, gateways
- Converts data between use cases and external layers
- Database, web, external API adapters
- Web frameworks, database engines
- Details, not core to the business
The most important rule: Source code dependencies can only point inward.
External layers can depend on inner layers,
but inner layers cannot depend on outer layers.
✅ Independent of Frameworks - Core business logic doesn't depend on frameworks ✅ Highly Testable - Inner layers can be tested without external dependencies ✅ Independent of UI - UI can change without affecting business logic ✅ Independent of Database - Can switch databases without changing business logic ✅ Long-term Maintainability - Architecture facilitates long-term sustainability ✅ Clear Dependency Direction - Inward dependencies make structure obvious ✅ Easy to Extend - New features fit naturally into the structure
❌ Steep Learning Curve - More complex than simpler patterns ❌ Overhead - May be overkill for small projects ❌ Abstract Concepts - Requires good understanding of SOLID principles ❌ Initial Development Speed - Takes longer to set up initially ❌ Team Discipline - Requires team commitment to maintain boundaries
Choose Clean Architecture if:
- ✅ Your business logic is complex and evolving
- ✅ Long-term maintenance is a high priority
- ✅ Multiple teams will work on the project
- ✅ You need strong architectural boundaries
- ✅ Testability is critical
- ✅ You want framework independence
Don't Choose Clean Architecture if:
- ❌ Your project is small and simple
- ❌ You need to develop very quickly
- ❌ Your team is new to architecture concepts
- ❌ Project scope is clear and unlikely to change
- ❌ Overhead is not justified by project complexity
Domain/
├── Entities/
│ ├── User.cs
│ └── Product.cs
├── Interfaces/
│ ├── IUserRepository.cs
│ └── IProductRepository.cs
└── BusinessRules/
Application/
├── UseCases/
│ ├── CreateUser/
│ │ ├── CreateUserUseCase.cs
│ │ ├── CreateUserRequest.cs
│ │ └── CreateUserResponse.cs
│ └── GetUser/
├── Interfaces/
├── Ports/
└── Services/
Infrastructure/
├── Repositories/
│ ├── UserRepository.cs
│ └── ProductRepository.cs
├── Database/
│ ├── ApplicationDbContext.cs
│ └── Migrations/
└── ExternalServices/
Presentation/
├── Controllers/
│ ├── UsersController.cs
│ └── ProductsController.cs
├── Presenters/
└── ViewModels/
Web/
└── Program.cs
Vertical Slice Architecture organizes code by features rather than technical layers. Each feature is a complete vertical slice through all layers of the application. This approach aligns well with Domain-Driven Design and microservices thinking.
Features/
├── Feature1/
│ ├── Commands/
│ ├── Queries/
│ ├── Models/
│ ├── Handlers/
│ ├── Validators/
│ ├── Services/
│ └── Infrastructure/
├── Feature2/
│ ├── Commands/
│ ├── Queries/
│ ├── Models/
│ └── ... (similar structure)
└── SharedKernel/
├── Behaviors/
├── Exceptions/
├── Validators/
└── Infrastructure/
- Each feature is self-contained
- Contains everything needed for that feature
- Minimal dependencies on other features
- Can be developed in parallel
- Commands - Change state
- Queries - Retrieve state without side effects
- Implements CQRS (Command Query Responsibility Segregation)
- Decouples read and write operations
- Cross-cutting concerns
- Infrastructure utilities
- Common validators and behaviors
- Event handlers
✅ Rapid Feature Delivery - Each feature can be developed independently ✅ Holistic Feature Understanding - All related code in one place ✅ Easy Parallelization - Multiple teams work on different features ✅ Reduced Integration Issues - Features are developed end-to-end ✅ Quick Feedback Loops - Features can be deployed independently ✅ Technology Flexibility - Different features can use different technologies ✅ Scalability - Features can be extracted to microservices
❌ Code Duplication - Similar logic might be duplicated across features ❌ Learning Curve - Requires understanding of CQRS and event handling ❌ Shared Kernel - Can become a dumping ground for shared code ❌ Feature Coupling - Features might become inadvertently coupled ❌ Complex Queries - Cross-feature queries can be complex
Choose Vertical Slice if:
- ✅ You're using Agile/Scrum methodology
- ✅ Features are relatively independent
- ✅ Rapid delivery and feedback is critical
- ✅ Your team is cross-functional
- ✅ You want to enable quick feature deployment
- ✅ Future microservices migration is likely
Don't Choose Vertical Slice if:
- ❌ Business logic is highly shared across features
- ❌ You need strict architectural consistency
- ❌ Your team is small or inexperienced
- ❌ Features are highly interdependent
- ❌ You need to minimize code duplication
Features/
├── Users/
│ ├── Commands/
│ │ ├── CreateUserCommand.cs
│ │ └── UpdateUserCommand.cs
│ ├── Queries/
│ │ ├── GetUserByIdQuery.cs
│ │ └── GetAllUsersQuery.cs
│ ├── Models/
│ │ └── UserModel.cs
│ ├── Services/
│ │ └── UserService.cs
│ ├── Events/
│ │ └── UserCreatedEvent.cs
│ └── UserFeatureModule.cs
├── Products/
│ ├── Commands/
│ ├── Queries/
│ ├── Models/
│ └── ... (similar structure)
└── SharedKernel/
├── Behaviors/
│ ├── ValidationBehavior.cs
│ └── LoggingBehavior.cs
├── Events/
├── Exceptions/
├── Utilities/
└── Infrastructure/
| Aspect | Layered | Clean | Vertical Slice |
|---|---|---|---|
| Complexity | Low | High | Medium |
| Learning Curve | Shallow | Steep | Medium |
| Development Speed | Fast | Slower initial | Fast |
| Testability | Good | Excellent | Good |
| Scalability | Medium | High | High |
| Team Independence | Partial | Good | Excellent |
| Maintainability | Good | Excellent | Good |
| Technology Flexibility | Medium | High | Highest |
| Feature Independence | Low | Medium | High |
| Code Reuse | Good | Good | Medium |
| Microservices Ready | No | Partial | Yes |
| Large Teams | Moderate | Excellent | Excellent |
| Project Scope | Well-defined | Evolving | Iterative |
Size:
- Small (< 5 modules) → Layered
- Medium (5-10 modules) → Layered or Vertical Slice
- Large (> 10 modules) → Clean or Vertical Slice
Complexity:
- Simple → Layered
- Medium → Vertical Slice
- Complex → Clean
Lifespan:
- Short-term → Layered
- Medium-term → Vertical Slice
- Long-term → Clean
Experience:
- Junior team → Layered
- Mixed team → Vertical Slice
- Senior team → Clean or Vertical Slice
Structure:
- Single team → Layered
- Multiple teams → Vertical Slice
- Large distributed teams → Clean + Vertical Slice
Methodology:
- Waterfall → Layered or Clean
- Agile → Vertical Slice
- Hybrid → Any option
Business:
- Stable requirements → Layered
- Evolving requirements → Clean or Vertical Slice
- Unclear requirements → Vertical Slice
Technical:
- Simple domain → Layered
- Complex domain → Clean
- Independent features → Vertical Slice
Deployment:
- Single deployment → Layered
- Modular deployment → Vertical Slice
- Microservices → Vertical Slice + Clean
START
│
├─ Is this a small project with clear scope?
│ └─ YES → LAYERED ✓
│ └─ NO → Continue
│
├─ Is long-term maintenance critical?
│ └─ YES → CLEAN ✓
│ └─ NO → Continue
│
├─ Are features relatively independent?
│ └─ YES → VERTICAL SLICE ✓
│ └─ NO → Continue
│
├─ Is rapid delivery important?
│ └─ YES → VERTICAL SLICE ✓
│ └─ NO → CLEAN or LAYERED
│
└─ Default → LAYERED (safe choice)
Projects typically evolve through these patterns:
-
Start with Layered
- Simple, familiar structure
- Good for getting started
- Fast initial development
-
Migrate to Clean (if complexity grows)
- Better boundaries
- Improved testability
- Long-term maintainability
-
Extract to Vertical Slice (if scale grows)
- Independent feature development
- Microservices extraction
- Multiple team collaboration
Regardless of which architecture you choose:
- Loose coupling between components
- Easy to test and mock
- Flexible replacements
- Each component has one responsibility
- Easier to understand and modify
- Reduced ripple effects
- Hide implementation details
- Enable technology switching
- Reduce coupling
- Clear file and folder names
- Obvious component purposes
- Easy navigation
- Architecture diagrams
- Decision logs
- Setup guides
- Unit tests for business logic
- Integration tests for components
- End-to-end tests for features
- Enforce architectural decisions
- Share knowledge
- Catch deviations early
There's no "best" architecture—only the best architecture for your specific context. Use this guide to understand the tradeoffs and make an informed decision for your project.
Remember:
- Start simple - Layered architecture is a safe starting point
- Evolve deliberately - Move to more complex patterns only when needed
- Document decisions - Keep track of why you chose a particular architecture
- Remain flexible - Be willing to refactor as your understanding grows
Happy Architecting! 🏗️