Clean Architecture in ASP.NET Core
Clean Architecture, also known as Onion Architecture, is a software design principle that emphasizes separation of concerns, testability, and maintainability. It achieves this by organizing your application into layers, with each layer having a specific responsibility and dependency direction.
Core Layers
1. Domain Layer (Core)
- Purpose: The heart of your application, containing your business rules and domain models.
- Contents:
- Entities: Represent the core concepts of your domain (e.g., Person, Order, Product).
- Value Objects: Immutable objects representing concepts like Money, Address, or EmailAddress.
- Domain Services: Encapsulate complex business logic or operations that involve multiple entities.
- Interfaces (Contracts): Define the contracts for repositories and other dependencies.
- Dependencies: None. The domain layer is independent of any external infrastructure or frameworks.
2. Application Layer
- Purpose: Orchestrates the use cases of your application.
- Contents:
- Use Cases (Application Services): Implement the high-level use cases or operations of your system (e.g.,
CreatePerson,GetPersonById). - DTOs (Data Transfer Objects): Represent data structures used for communication between layers.
- Interfaces (Contracts): Define the contracts for infrastructure services (e.g., repositories, email services).
- Use Cases (Application Services): Implement the high-level use cases or operations of your system (e.g.,
- Dependencies: Depends on the Domain Layer.
3. Infrastructure Layer
- Purpose: Implements the technical details of how your application interacts with external systems (databases, file systems, email services, etc.).
- Contents:
- Repositories: Implement the data access logic for your entities.
- Services: Implement the interfaces defined in the application layer for interacting with external systems.
- Dependencies: Depends on the Application Layer and any external libraries or frameworks needed for infrastructure tasks.
4. Presentation Layer (UI)
- Purpose: Handles user interaction and presentation logic.
- Contents:
- Controllers: Handle HTTP requests, interact with use cases, and return views or API responses.
- Views: Render the user interface.
- View Models: Shape data for presentation in views.
- Dependencies: Depends on the Application Layer.
5. Tests
- Purpose: Ensures the correctness of your application’s behavior.
- Contents:
- Unit Tests: Test individual units of code in isolation.
- Integration Tests: Test the interaction between multiple components.
- End-to-End Tests: Test the entire application flow from the user’s perspective.
Dependency Direction
- Inner Layers to Outer Layers: Dependencies flow from the inner layers (Domain) to the outer layers (Presentation).
- Abstraction: Outer layers depend on abstractions (interfaces) defined in the inner layers.
Sample Code Implementation (Persons Records Management)
Domain Layer (Core)
public class Person
{
public Guid PersonId { get; set; }
public string Name { get; set; }
// ... other properties
}
public interface IPersonsRepository
{
Task<Person> AddPerson(Person person);
Task<List<Person>> GetAllPersons();
// ... other CRUD operations ...
}Application Layer
public class PersonDto { /* ... */ } // DTO for transferring person data
public interface IPersonsService
{
Task<PersonDto> CreatePerson(PersonDto personDto);
Task<List<PersonDto>> GetAllPersons();
// ... other operations ...
}
public class PersonsService : IPersonsService
{
private readonly IPersonsRepository _personsRepository;
public PersonsService(IPersonsRepository personsRepository)
{
_personsRepository = personsRepository;
}
public async Task<PersonDto> CreatePerson(PersonDto personDto)
{
// Validation, mapping, etc.
var person = new Person { /* ... map from DTO ... */ };
var createdPerson = await _personsRepository.AddPerson(person);
return createdPerson.ToDto(); // Map back to DTO
}
// ... other methods ...
}Infrastructure Layer
public class PersonsRepository : IPersonsRepository
{
private readonly MyDbContext _dbContext;
public PersonsRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Person> AddPerson(Person person)
{
_dbContext.Persons.Add(person);
await _dbContext.SaveChangesAsync();
return person;
}
// ... other methods ...
}Presentation Layer (UI) - Controller
public class PersonsController : Controller
{
private readonly IPersonsService _personsService;
public PersonsController(IPersonsService personsService)
{
_personsService = personsService;
}
[HttpPost]
public async Task<IActionResult> Create(PersonDto personDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var createdPerson = await _personsService.CreatePerson(personDto);
return CreatedAtAction(nameof(GetPersonById), new { id = createdPerson.PersonId }, createdPerson);
}
// ... other actions ...
}Explanation
- The Domain layer defines the core
Personentity and theIPersonsRepositoryinterface. - The Application layer defines the
IPersonsServiceinterface and thePersonsServiceimplementation. - The Infrastructure layer contains the
PersonsRepositorythat implements the repository interface and interacts with the database. - The Presentation layer has the
PersonsControllerthat handles requests, uses thePersonsService, and returns appropriate responses.
Key Points to Remember
Clean Architecture in ASP.NET Core
- Separation of Concerns: Decouples the different parts of your application into well-defined layers.
- Dependency Inversion Principle (DIP): Inner layers define abstractions, outer layers depend on them.
- Testability: Each layer can be tested in isolation using mocks or stubs.
- Maintainability: Easier to modify and extend as requirements evolve.
- Flexibility: You can swap out implementations in outer layers without affecting core business logic.
Layers Summary
| Layer | Purpose | Contents | Depends On |
|---|---|---|---|
| Domain (Core) | Business rules & domain models | Entities, Value Objects, Interfaces | None |
| Application | Orchestrates use cases | Services, DTOs, Interfaces | Domain |
| Infrastructure | Technical implementations | Repositories, External Services | Application |
| Presentation (UI) | User interaction | Controllers, Views, ViewModels | Application |
| Tests | Ensures correctness | Unit, Integration, E2E tests | All layers |
Benefits
- Improved Maintainability: Changes are isolated to specific layers.
- Testability: Each layer is easily testable in isolation.
- Flexibility: Swapping implementations in outer layers doesn’t affect the core.
- Focus on Business Logic: The domain layer is at the center, emphasizing the core of your application.
Interview Tips
- Explain the Layers: Be able to clearly explain the purpose of each layer.
- Dependency Direction: Emphasize that dependencies flow inwards towards the Domain layer.
- Abstractions: Highlight the importance of using interfaces to achieve loose coupling.
- Real-World Scenarios: Discuss how you’ve used or would use Clean Architecture.
- Benefits: Articulate the advantages in terms of maintainability, testability, and flexibility.
Remember
- Trade-offs: Clean Architecture adds some complexity, so consider if it’s appropriate for your project.
- Focus on the Domain: The domain layer should be the most important and stable part.
- Continuous Refactoring: Continuously refactor to maintain separation of concerns.