我正在使用ASP.NET MVC开发基于Web的应用程序。我正在尝试使用丰富的域模型而不是瘦/贫模型。
我已经按照洋葱建筑的方式建立了我的解决方案。不同的项目如下:
****使用dapper完成数据访问,IDbContext是2个简单命令查询接口的包装器。我已将每个查询隔离为单独的类。
为了便于讨论,我将收集一小部分申请。
我有一个版本化的文档库,其中包含文档以及标签,权限等其他元数据
我的文档对象的简化模型如下所示
我希望在域对象中定义操作,因为每个操作都涉及业务逻辑。 让我把“删除”作为一个操作。需要执行操作
如上例所示,我需要数据库上下文来完成此操作。 我目前正在考虑建模的方法是让域对象具有IDbContext,它可以执行公开的查询。
在我的控制器类中,我调用域对象并执行操作。
我不确定在域对象中传递IDbContext是否正常?如果没有更好的方法来模拟这个?
我不相信拥有单独的服务层,因为 1)对于大多数情况,控制器充当服务层的第一层 2)服务层只是将相同的方法从域复制到另一个类
让我知道如何改进这种设计。
答案 0 :(得分:2)
注入IDbContext
类似于制定域模型的主要原则,该模型应仅在检索和存储域实体时负责业务逻辑,这是基础架构层的责任。是的,你通过接口注入它,隐藏实际的实现,但它让你的域模型知道一些存储。
此外,删除Document
所需的步骤也不一定属于Document对象。让我们考虑使用用户权限的第一步和以下情况:
对于第一种情况,用户和要删除的文档之间可能没有连接。管理员用户只允许做任何事情。这就像一个典型的例子,有两个银行账户和一个转账的操作,涉及两个账户,但不是他们的责任。这是域服务到位的时候。请不要将它们与服务层服务混淆。域服务是域模型的一部分,仅负责业务逻辑。
所以如果我是你,我会用DeleteDocument
方法创建一个新的域服务。这应该从上面的前三个步骤接受User
和Document
作为参数。第四步应该由您的存储库完成。
我没有看到添加存储库的重要价值
但从域名模型的角度来看,你已经拥有了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
实体具有不同的属性,例如DepartureCity
,ArrivalCity
。 Flight
实体还应提及席位列表。 Seat
也可以是一个单独的实体
属性为Class
(商业,经济等),Type
(isle,row,middle)等。因此,预订座位需要与不同的参与者互动,例如Flight
和{ {1}}但我们不需要域名服务。如果不被视为Seat
的子对象,那么自然Seat
属性就毫无意义。您甚至不可能从Flight
上下文中查询Seat
实体。因此,保留Flight
是Seat
实体的责任,可以将保留逻辑放到Flight
类中。请注意,这只是一个示例,尝试解释何时需要创建域服务,真正的系统可以完全另一种方式建模。因此,请尝试按照以下三个基本步骤来决定是否需要域名服务:
- 服务执行的操作是指自然不属于实体或值对象的域概念。
- 执行的操作是指域中的其他对象。
- 操作是无状态的。
醇>
我正在从控制器访问dbcontext,该控制器是应用程序/服务层而不是域/业务层。域模型只处理业务逻辑,它不应该知道任何持久性逻辑,从上面的例子可以看出DocumentService没有dbcontext的引用。