【问题标题】:Is it ok to have database context in my domain model在我的域模型中有数据库上下文是否可以
【发布时间】:2014-10-18 18:47:10
【问题描述】:

我正在使用 ASP.NET MVC 开发基于 Web 的应用程序。我正在尝试拥有丰富的领域模型,而不是瘦/贫血模型。

我按照 Onion 架构对我的解决方案进行了建模。不同的项目如下:

  • {}.Domain.Core - 包含域对象和接口,例如在基础设施层实现的 IDbContext
  • {}.Database - 是数据库项目
  • {].Infrastructure - 包含日志记录、数据访问等的实现。
  • {}.Web - 视图和控制器

**** 数据访问是使用 dapper 完成的,IDbContext 是围绕 2 个简单命令、查询接口的包装器。我已将每个查询隔离为单独的类。

为了便于讨论,我只介绍了应用程序的一小部分。

我有一个版本化的文档库,其中包含文档以及标签、权限等其他元数据

我的文档对象的简化模型如下所示

我希望在域对象中定义操作,因为每个操作都涉及业务逻辑。 让我把“删除”作为一个操作。需要执行的操作

  • 验证用户是否有删除权限
  • 检查是否没有将受到此删除影响的关联
  • 检查是否没有正在进行的工作流
  • 在事务中从数据库中删除实际项目

如上例所示,我需要数据库上下文来完成此操作。 我目前考虑的方式是建模是否让域对象具有 IDbContext,它可以执行公开的查询。

在我的控制器类中,我调用域对象并执行操作。

我不确定在域对象中传递 IDbContext 是否可以?如果不是,有什么更好的建模方法?

我不相信有一个单独的服务层,因为 1)控制器在大多数情况下充当服务层的第一层 2)服务层只是将相同的方法从域复制到另一个类

让我知道如何改进这个设计。

【问题讨论】:

    标签: asp.net-mvc oop architecture onion-architecture


    【解决方案1】:

    像这样注入IDbContext 会破坏域模型的主要原则,它应该只负责业务逻辑,而检索和存储域实体是基础设施层的责任。是的,你通过接口注入它,隐藏了实际的实现,但它让你的域模型知道一些存储。

    此外,删除Document 所需的上述步骤也不完全属于 Document 对象。让我们考虑一下用户权限的第一步和以下情况:

    • 应允许具有管理员角色的用户删除任何文档
    • 应允许文档所有者删除文档

    对于第一种情况,用户和要删除的文档之间可能没有连接。管理员用户只被允许做任何事情。这就像一个经典的例子,有两个银行账户和一个转账操作,涉及两个账户,但不是他们的责任。这是域服务到位的时候。请不要将它们与服务层服务混淆。领域服务是领域模型的一部分,只负责业务逻辑。

    所以如果我是你,我会使用DeleteDocument 方法创建一个新的域服务。这应该执行上面的前三个步骤,接受 UserDocument 作为参数。第四步应该由您的存储库完成。不知道你说的是什么意思

    我没有看到添加存储库有太多价值

    但从领域模型的角度来看,您已经拥有一个 IDbContext。我假设您的意思是为每个实体实现存储库或单独的存储库的某种模式。从长远来看,您在控制器中的伪代码应如下所示:

    var user = bdContext<User>.SelectById(userId);
    var document = bdContext<Document>.SelectById(docId);
    var docService = new DocumentService();
    docService.DeleteDocument(document, user);  //  Throw exception here if deletion is not allowed
    bdContext<Document>.Delete(document);
    

    如果您希望在应用程序的许多地方都需要此逻辑,您可以将其封装在服务层服务中。

    如果您想了解有关领域建模的更多信息,我建议您阅读关于 DDD 的 Eric Evans book。这里详细讨论实体、值对象、领域服务等的含义。

    对评论的回答: 并非如此,域服务是域的一部分,因此实现和接口也是域的一部分。两个或多个对象必须相互交互的事实不足以创建域服务。让我们以航班预订系统为例。您有一个具有不同属性的 Flight 实体,例如 DepartureCityArrivalCityFlight 实体还应该有对席位列表的引用。 Seat 也可以是一个单独的实体 属性如Class(商务、经济等)、Type(小岛、行、中间)等。因此预订座位需要与不同的实体进行交互,例如FlightSeat,但我们不需要这里不需要域服务。从本质上讲,Seat 属性如果不被视为Flight 的子对象,则毫无意义。您甚至不太可能从Seat 上下文之外查询Seat 实体。所以保留SeatFlight 实体的责任,可以将保留逻辑放到Flight 类中。请注意,这只是一个示例,用于解释当我们需要创建域服务时,可以完全以另一种方式对真实系统进行建模。因此,只需尝试按照以下三个基本步骤来决定您是否需要域服务:

    1. 服务执行的操作是指一个领域概念,它自然不属于实体或值对象。
    2. 执行的操作引用域中的其他对象。
    3. 操作是无状态的。

    我正在从控制器访问 dbcontext,该控制器是应用程序/服务层而不是域/业务层。领域模型只处理业务逻辑,它不应该知道任何持久性逻辑,从上面的示例中您可以看到 DocumentService 没有对 dbcontext 的引用。

    【讨论】:

    • 感谢您的详细回复。据我了解,您正在创建一个域服务,其中一个或多个域对象需要交互(在大多数情况下都是如此)。在这一层中,您正在访问 dbcontext。所以核心域知道域服务的接口,但实际实现在外面?所以我的控制器仍然与域对话,域对象与域服务交互以执行任何操作。我的理解正确吗?
    • 感谢您清除我对域服务和应用程序服务的理解。顺便说一句,我曾经尝试过 Eric Evans 的 DDD,但因为我觉得它太干了而放弃了。但是有一些实际的工作要涉及,让我再试一次。
    • 我在您的回复中看到您正在使用 - bdContext.SelectById(userId); - bdContext.SelectById(docId);我的困惑是,这些不也是领域对象需要负责的操作吗?例如getDocumentByID 可能需要检查我是否真的可以访问该文档。还有一件事,假设将来我需要将操作作为 API 层公开给另一个系统以供使用(不是通过 Web,而是通过正常的程序集分发),您更喜欢编写单独的外观还是域服务应该作为 api 工作?
    • @Sharath Chandra 是的,当现在有工作要做时,Eric Evans 的 DDD 不是一个选项,尽管试试这个缩短版,它只有一百页 - ddd-cqrs-base-project.googlecode.com/files/…
    • @Sharath Chandra 关于您的第二个问题。从存储中获取对象与业务逻辑无关。这个 id 必须来自某个地方:用户在 UI 上检查了一个项目,有来自另一个对象的引用,等等。所以如果你不能通过 id 找到它,它要么是一个一致性错误,例如你删除了一个对象但忘记了更新其他对象引用或其他用户在 UI 上为您显示后将其删除,这对于 Web 应用程序很常见,但问题仍然与平台或存储相关。
    猜你喜欢
    • 2012-10-14
    • 2019-12-22
    • 1970-01-01
    • 2010-11-13
    • 1970-01-01
    • 2011-09-28
    • 2010-09-15
    • 1970-01-01
    • 2019-01-23
    相关资源
    最近更新 更多