ddd要求domain层完全隔离基础设施,故myapp.domain不得引用microsoft.entityframeworkcore;order类不可加[table]或实现ientitytypeconfiguration;irepository接口方法参数返回值须为纯领域类型,禁用iqueryable;业务规则必须内聚于领域对象自身方法中。

Domain 层里出现 Microsoft.EntityFrameworkCore 引用,就不是 DDD,只是带业务名词的三层架构。
Domain 项目为什么不能引用 EF Core
领域层(如 MyApp.Domain)必须只依赖 net6.0 或 netstandard2.1,不能引入任何基础设施库。一旦你在 Order 类上加了 [Table("Orders")],或让类实现 IEntityTypeConfiguration<order></order>,领域模型就和 EF Core 绑死了。
后果很直接:
- 换数据库(比如从 SQL Server 切到 Cosmos DB)时,得重写所有实体类
- 想加事件溯源,发现
AggregateRoot已被DbSet<t></t>的生命周期逻辑污染 - 单元测试里 mock 不了
Order,因为构造函数偷偷 new 了DbContext
正确做法是:把 DbContext 和所有 DbSet<t></t> 全放在 Infrastructure 层,Domain 层只暴露 IRepository<order></order> 接口,且该接口方法参数/返回值只能是 Order、IEnumerable<order></order>、Option<order></order> 这类纯领域类型。
IOrderRepository.GetAll() 返回 IQueryable 就越界了
IQueryable<order></order> 是 EF Core 的查询构建器,不是领域概念。它允许调用方拼接 .Where()、.OrderBy(),导致业务规则分散在应用服务甚至控制器里。
常见错误写法:
public interface IOrderRepository
{
IQueryable<order> GetAll(); // ❌ 泄漏 IQueryable,等于把 EF 暴露给上层
Order GetById(Guid id);
}</order>
替代方案只有两条路:
- 用
Specification<order></order>封装查询意图,例如repo.Find(new ConfirmedOrdersInLastWeekSpec()) - 拆细接口,按用例建查询服务:
IOrderQueryService.GetConfirmedByCustomer(string customerId),返回IReadOnlyList<order></order>
关键判断标准:如果某个查询方法名里带 By + 字段名(如 FindByStatus),而且返回 IQueryable,那它已经越界了。
应用服务里写 if (order.Total > 1000) 就说明领域逻辑没落地
应用服务(ApplicationService)不是业务逻辑容器,它只做三件事:取数据 → 调领域对象 → 存结果。所有校验、状态转换、规则执行,必须收在 Order 自身的方法里。
错误示例:
if (order.Total > 1000)
{
order.ApplyPremiumDiscount();
}
正确写法是让领域对象自己决定“能不能做”和“做了之后变什么样”:
public class Order
{
public decimal Total { get; private set; }
public OrderStatus Status { get; private set; }
public void Confirm()
{
if (Status != OrderStatus.Draft)
throw new InvalidOperationException("Only draft orders can be confirmed");
Status = OrderStatus.Confirmed;
// 不做任何 Save、不碰 DbContext、不发事件
}
public void ApplyDiscount(DiscountRule rule)
{
if (Total
<p>保存动作交给应用服务统一协调,由仓储实现落地;折扣规则也应封装进 <code>Order</code> 内部,而不是由上层判断后调用。</p>
<h3>仓储接口返回 OrderDto 或 new 出领域对象就是破防点</h3>
<p>错误示例:<code>IOrderRepository.FindByCustomerId(int id)</code> 返回 <code>IQueryable<order></order></code>,或更糟——返回 <code>OrderDto</code>。</p>
<p>为什么危险:</p>
-
IQueryable把 LINQ 表达式树泄漏到上层,应用服务可能写出.Where().OrderBy().Skip().Take(),仓储就不再是“按 ID 加载/保存聚合”的抽象,而退化成数据访问层包装 - 返回
OrderDto意味着领域层开始为展示层妥协,值对象、不变量、业务约束全被绕过 - 后续加缓存、读写分离、CQRS 时,所有地方都要改
推荐做法:
- 仓储只提供明确语义的方法:
GetById、Add、Update - 复杂查询走独立的
IOrderQueryService,不混进仓储接口 - 领域对象创建必须受控:用工厂(
IOrderFactory)或静态工厂方法(Order.Create()),确保构造时满足所有不变量
最易被忽略的是:领域对象的构造函数不该接受原始参数堆砌(如一堆 string、int),而应强制通过值对象或工厂入参——否则“不变量检查”就只活在文档里。










