Controller-Service-Repository模式中DTO的应用及相关疑问
Great question—this is one of the most frequent "best practice" debates when building layered applications, especially when you’re prioritizing hiding internal domain details from external clients. Let’s break down your two core questions clearly:
1. Should Controllers Only Receive DTOs?
Short answer: Yes, this is the recommended approach for most production-grade applications. Here’s why:
- API Contract Stability: DTOs let you define a fixed, client-facing contract that doesn’t tie directly to your internal domain model. If your domain model evolves (e.g., adding internal-only fields, changing relationships), your API can stay consistent without breaking external clients.
- Information Hiding: Domain models often contain sensitive or internal-only data (like audit fields, business logic flags, or intermediate state) that external clients don’t need (and shouldn’t have access to). Using DTOs ensures you only expose what’s necessary.
- Decoupling: Controllers act as the entry point to your application—keeping them focused on DTOs decouples your external API from your internal business logic layer. This makes it easier to adapt to different client needs (e.g., mobile vs. web clients using different DTOs) without changing core domain code.
The only exception might be tiny, trivial applications where the overhead of DTOs outweighs the benefits—but even then, it’s a good habit to build for future scalability.
2. Where Should DTO ↔ Domain Model Conversion Happen?
The general rule of thumb is to keep conversion logic at the controller layer (or in dedicated mapper classes that controllers use), not the service layer. Here’s the breakdown for both directions:
Incoming Requests (Client → Controller → Service)
- Controllers receive DTOs from clients, convert them to domain models (or business command objects), and pass those to the service layer.
- Why? The service layer should be focused solely on business logic, not on adapting to external API formats. By passing domain models to services, you keep services reusable—they can be called by controllers, internal jobs, or other services without needing to handle DTO-specific logic.
Outgoing Responses (Service → Controller → Client)
- Services return domain models (or domain-specific result objects) to controllers, which then convert them to DTOs before sending back to clients.
- Why? This keeps the service layer ignorant of client-facing requirements. If you ever need to change the DTO structure (e.g., adding a new field for a mobile client), you only modify the controller/mapper, not the core business logic.
Pro tip: Avoid writing conversion code directly in controllers or services. Use a dedicated mapper library like MapStruct, or create your own DomainToDtoMapper/DtoToDomainMapper classes to keep this logic clean and testable.
Key Takeaway
The goal is to maintain a clear boundary between your external API (handled by controllers with DTOs) and your internal business logic (handled by services with domain models). This isolation makes your application more maintainable, scalable, and resilient to changes in either layer.
内容的提问来源于stack exchange,提问作者kcon123

