为什么存储库的.Add
方法通常实现为接受要添加的实体实例,而.Id
已经被“设置”了(尽管可以通过反射再次设置),这应该是仓库的责任?
将其实现为.AddAndCreate
会更好吗?
例如,给定一个Person
实体:
public class Person
{
public Person(uint id, string name)
{
this.Id = id;
this.Name = name;
}
public uint Id { get; }
public string Name { get; }
}
为什么存储库通常实现为:
public interface IRpository<T>
{
Task<T> AddAsync(T entity);
}
而不是:
public interface IPersonsRpository
{
Task<Person> AddAndCreateAsync(string name);
}
答案 0 :(得分:3)
为什么通常将存储库实现为...?
一些原因。
从历史上看,domain-driven-design
受引入该术语的埃里克·埃文斯(Eric Evans)书的影响很大。埃文斯在那里提出,存储库提供集合语义,从而提供“内存集合的错觉”。
向String
的集合中添加Name
甚至是Person
并没有多大意义。
从更广泛的意义上讲,弄清楚如何从一组参数中重构实体是与存储分开的责任,因此,去那里是没有意义的(请注意:存储库通常最终具有重构的责任来自一些存储的纪念品的实体,因此它不是完全外来的,但是通常还有一个额外的抽象,“工厂”确实可以完成工作。)
使用通用的存储库接口通常很有意义,因为通过检索/存储操作与集合的各个元素进行交互不需要太多的自定义技巧。存储库可以支持针对不同种类的实体的自定义查询,因此明确指出这一点可能很有用
public interface IPersonRepository : IRepository<Person> {
// Person specific queries go here
}
最后,id
...的事实是,身份作为一个概念具有很多“取决于”的含义。在某些情况下,对于存储库来说,为实体分配ID是有意义的-例如,使用数据库生成的唯一密钥。通常,您将需要在存储库外部控制标识符。马匹。
答案 1 :(得分:1)
这个问题已经有了一个很好的答案,我只想补充一下自己的想法。 (它将与先前的答案重复一些,因此,如果这是一件坏事,请告诉我,我将其删除:))。
ID生成的职责可以属于组织或系统的不同部分。
有时,某些特殊规则(例如Social Security Number)会生成ID。该数字可用于系统中 Person 的ID,因此在创建 Person 实体之前,此代码必须从特定的 SSNGenerator 生成。强>服务。
我们可以使用随机生成的ID,例如UUID。 UUID可以在存储库之外生成,并在创建期间分配给实体,并且存储库只会将其存储(添加,保存)到数据库中。
数据库生成的ID非常有趣。您可以在RDBMS中使用 Sequential ID ,在MonogoDB中使用UUID-ish或某些Hash。在这种情况下,将ID生成的 Responsibility (责任)分配给了数据库,因此只有在存储实体后才可以进行创建,而不是在创建实体时进行。 (我在这里允许自己自由,因为您可以在保存交易或阅读最后一笔交易之前生成它。但是我想在这里概括一下,避免讨论带有竞争条件和冲突的情况。)这意味着在保存完成之前,您的实体没有身份。这是一件好事吗?当然这取决于:)
此问题是leaky abstractions的一个很好的例子。
实施解决方案时,有时所使用的技术会对其产生影响。您将不得不处理以下事实:例如,ID是由数据库生成的,该数据库是您的 Infrastructure 的一部分(如果您在代码中定义了这样的层)。即使使用RDBMS,也可以通过使用s UUID来避免这种情况,但是必须对这些ID进行 join (再次使用技术特定的东西:)),因此有时人们喜欢使用默认值。 / p>
您可以拥有 Save 方法,而不是使用 Add 或 AddAndCreate 来完成相同的工作,而只是有些人不同喜欢。该存储库确实经常被定义为“ 内存中的存储”,但这并不意味着我们必须严格遵守它(大多数时候这样做是一件好事,但仍然...)。
如前所述,如果您的数据库生成ID,则存储库似乎是分配ID(在存储之前或之后)的一个不错的选择,因为它是与数据库对话的一个对象。
如果您使用事件,则生成ID的方式可能会影响事物。例如,假设您要拥有 UserRegisteredEvent ,且UserID为s属性。如果使用数据库生成ID,则必须先存储 User ,然后创建并存储/分发事件或执行某种操作。另一方面,如果您事先生成ID,则可以将事件和实体保存在一起(在事务或同一文档中都没有关系)。有时候这可能会很棘手。
背景,技术和框架的经验,文学,学校和工作中术语的接触程度会影响我们对事物的看法以及对我们而言更好的术语。另外,我们(大部分时间)都在团队中工作,这可能会影响我们为事物命名的方式以及实现方式。
答案 2 :(得分:1)
使用Martin Fowler's定义:
存储库在域和数据映射层之间进行中介, 就像内存中的域对象集合一样。客户对象 以声明方式构造查询规范并将其提交给 满足需求的存储库。可以将对象添加到对象或从中删除 存储库,因为它们可以从简单的对象集合中获得,并且 存储库封装的映射代码将执行 在幕后进行适当的操作。从概念上讲,存储库 封装保存在数据存储中的对象集, 对它们执行的操作,提供了更面向对象的视图 持久层
存储库提供基础数据的面向对象视图(可以以其他方式存储在关系数据库中)。它负责将您的表映射到您的实体。
为对象生成ID是完全不同的责任...您可以决定在数据库,域或单独的服务中生成ID。无论在何处生成ID,存储库都应该在您的实体和表之间无缝映射它。
ID生成是其自身的责任,如果将其添加到存储库中,则会违反单一责任原则。