我正在为MVC应用程序构建一些存储库,我正在尝试在存储库之间划分责任的正确方法。在大多数情况下,这是显而易见的。但有一个特殊情况,我不确定答案是正确的。
此应用程序的用户需要为其员工跟踪多种类型的时间。为简单起见,我们只考虑两个。我会称他们为“时间卡”和“出勤”。这两者之间差异的确切性质并不重要,但您应该注意到最终用户将它们视为完全独立的数据。但我认为,他们认为这些数据完全是分开的数据的原因是他们过去从未真正有机会看到它们。两种类型的记录都有关于编辑记录的几乎完全不同的业务规则,但一般来说,它们也都是员工在特定时间所处位置的记录。这两种类型的时间记录都有很多共同的属性,例如总小时数和收集时间的雇员。这两种类型都有一些属性,这些属性对于单个类型是完全独特的。我们将这些“额外”属性保存在另一种类型的实例中。所以一般结构如下:
class TimeRecord
{
Person Employee { get; set; }
TimeSpan? Hours { get; set; }
}
class TimeCardData
{
TimeRecord Record { get; set; }
TProperty TimeCardProperty { get; set; }
}
class AttendanceData
{
TimeRecord Record { get; set; }
TProperty AttendanceProperty { get; set; }
}
所以问题是,这里需要多少个存储库?
只有一个存储库的设计会公开在一个列表中返回“时间卡”,“出勤”记录或两种类型的方法。这对于存储库的客户来说相当方便,但是,在我看来,它有成为一个非常胖的类的真正危险。我认为只是“时间卡”的存储库已经成为系统中最大的存储库之一,即使没有处理“出勤”也只是因为涉及复杂的业务规则。
另一种设计将有一个“时间卡”存储库和另一个“出勤”记录存储库。这具有以下优点:例如“时间卡”的业务规则本身就在某个地方。但是我也希望能够获得所有时间记录的列表,无论其类型如何。目前尚不清楚在这种情况下使用哪个存储库。既?
设计有一个“时间卡”存储库,另一个“出勤”记录存储库,第三个存储库提供所有时间记录的只读列表也是可能的。与2存储库设计一样,这具有以下优点:例如“时间卡”的业务规则本身就在某个地方。现在很清楚从哪里获得组合列表。但我发现从两个不同的存储库中获取相同的记录有点奇怪。
混合方法将使用单个存储库,但将任何业务规则代码(包括记录选择)移动到单独的类型中。在此示例中,单个“时间记录存储库”将聚合业务规则实现类的实例以用于“时间卡”和“出勤”时间。我认为这是我现在喜欢的方法。
我错过了什么?一种设计对另一种设计有任何令人信服的论据吗?
答案 0 :(得分:18)
至少据我所知,存储库是业务规则的一个地方。它们只是一个模仿集合的外观;在它们之下它们基本上是纯粹的数据访问(如果这是他们的工作,你可能不会持久存储任何存储库)。因此,出于“业务规则”的原因,不应考虑单独的存储库。
如果您的域对象实际上是单独的对象,那么您应该有单独的存储库。记住存储库是什么:它是一个外观。它模仿您的域名集合。请参阅此处,了解有关存储库的精彩博文:http://devlicio.us/blogs/casey/archive/2009/02/20/ddd-the-repository-pattern.aspx
存储库是一个外观;抽象。
那说......我认为你没有单独的对象。你在这里遇到的一些问题与存储库无关,而且与域和域的设计有关。两种类型的“时间卡”实际上是两种不同的东西,还是它们真的相同?
你说,“但我发现从两个不同的存储库中获取相同的记录有点奇怪。”
这告诉我他们实际上是相同的数据,以不同的方式表达。并且有办法处理这个问题。
如果确实如此,那么你所拥有的是一个公共基类的子类(可以很容易地在数据库中建模,并且可以使用NHibernate优雅地处理)。
我将举例说明我正在进行的项目。我有一种叫做“广播”的东西。这是一个基类;抽象。无法实例化。我有这个类的两个具体的具体类型:DeviceBroadcast和FileBroadcast。一个流从设备流式传输音频/视频(如DirectX采集卡),一个流传输来自文件源的音频/视频(如.mp3)。
我有一个返回Broadcast对象的存储库。我可以将它转换为FileBroadcast来操作有关FileBroadcast的特定信息,或者我可以出于同样的原因转换为DeviceBroadcast - 如果它是那种类型。 Broadcast不能同时是FileBroadcast和DeviceBroadcast类型。它必须是一个或另一个。
在数据库中,我将通用广播参数存储在Broadcast表中,然后将文件特定属性存储在FileBroadcast表中。 DeviceBroadcast表也是如此;分离。但是,当我通过存储库查询时,我只想要广播。这是我的根聚合对象,因此这是我的存储库。
Broadcast基类具有两个子类都使用的常用方法(如GetCommand()方法,该方法返回特定的命令行参数以启动VLC进程)。子类必须覆盖并实现该方法,因为它是抽象的。通过这种方式,FileBroadcast特有的“业务逻辑”包含在FileBroadcast类中。 DeviceBroadcast特有的“业务逻辑”包含在DeviceBroadcast类中。两者共有的任何逻辑都包含在超类Broadcast中。
你似乎有类似的情况,这就是我分享我的设计的原因。我认为它可能对你有用。
最重要的是,考虑一下您的域名和数据。如果您要通过单独的存储库获取重复数据,那么您需要更多地考虑如何设计域。不要让用户决定您的域名设计。他们从他们的角度了解这个领域。您所要做的就是能够以他们理解的方式向他们呈现数据。这并不意味着你必须有一个糟糕的设计;您可以在幕后拥有良好的设计,因为您的代码是必须使用域的东西。