为什么存储库的.Add
方法通常被实现为接受要添加的实体的实例,.Id
已经“设置”(尽管可以通过反射再次设置),这应该是repo的责任?
将它作为.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);
}
为什么存储库通常实现为......?
有几个原因。
从历史上看,domain-driven-design
深受Eric Evans推出这一术语的书的影响。在那里,埃文斯提出存储库提供了集合语义,提供了“内存集合的幻觉”。
将String
,甚至是Name
添加到Person
的集合中并没有多大意义。
更广泛地说,弄清楚如何从一组参数中重构一个实体是存储的一个单独责任,所以也许去那里是没有意义的(注意:存储库通常最终负责重构一个实体一些存储的纪念品,所以它不是完全陌生的,但通常还有一个额外的抽象,即“工厂”,它真正起作用。)
使用通用存储库接口通常是有意义的,因为通过检索/存储操作与集合的各个元素交互不应该需要大量的自定义工具。存储库可以支持不同类型实体的自定义查询,因此专门调用它可能很有用
public interface IPersonRepository : IRepository<Person> {
// Person specific queries go here
}
最后,id
......以及它的真实性在于,作为一个概念,身份有很多“它依赖”。在某些情况下,存储库可以将id分配给实体 - 例如,使用数据库生成的唯一密钥。通常,您希望控制存储库之外的标识符。马匹课程。
关于这个问题已经有了很好的答案,我只是想补充一些我的想法。 (它将包含前一个答案的一些重复,所以如果这是一个坏事只是让我知道,我将删除它:))。
ID生成的责任可以属于组织或系统的不同部分。
有时,ID会由一些特殊规则生成,例如Social Security Number。此数字可用于系统中Person的ID,因此在创建Person实体之前,必须从特定SSNGenerator服务生成此代码。
我们可以使用随机生成的ID,如UUID。 UUID可以在Repository之外生成,并在创建期间分配给实体,而Repository只会将其存储(添加,保存)到DB。
数据库生成的ID非常有趣。你可以像在MonogoDB中那样在RDBMS,UUID-ish中使用顺序ID,也可以使用一些Hash。在这种情况下,ID生成的责任分配给DB,因此只有在存储实体后才能创建它。 (我允许自己在这里自由,因为你可以在保存交易或阅读最后一个等之前生成它。但我喜欢在这里概括并避免讨论有竞争条件和碰撞的情况)。这意味着您在保存完成之前没有身份。这是一件好事吗?当然这取决于:)
这个问题是leaky abstractions的一个很好的例子。
当您实施解决方案时,有时使用的技术会影响它。您必须处理这样的事实:例如,ID是由您的数据库生成的,该数据库是您的基础结构的一部分(如果您在代码中定义了这样的层)。你也可以通过使用s UUID来避免这种情况,即使你使用RDBMS,但是你必须加入(这些技术特定的东西:))这些ID,所以有时人们喜欢使用默认值。
您可以使用Save方法而不是使用Add或AddAndCreate来执行相同的操作,而这只是某些人喜欢的不同术语。存储库确实经常被定义为“内存集合”,但这并不意味着我们必须严格遵守它(大部分时间都可以做到这一点,但仍然......)。
如上所述,如果您的数据库生成ID,则Repository似乎是分配ID的良好候选者(在存储之前),因为它是与DB通信的人。
如果您使用事件,则生成ID的方式会影响事物。例如,假设您希望UserRegisteredEvent具有UserID作为s属性。如果您使用数据库生成ID,则必须先存储用户,然后创建并存储/分发事件或执行某种操作。另一方面,如果您事先生成ID,则可以将事件和实体保存在一起(在事务中或在同一文档中无关紧要)。有时这可能会变得棘手。
背景,技术和框架的经验,文学,学校和工作中术语的接触会影响我们对事物的思考方式以及对我们来说听起来更好的术语。此外,我们(大部分时间)都在团队中工作,这可能会影响我们如何命名以及如何实现它们。
使用Martin Fowler's定义:
存储库在域和数据映射层之间进行调解,其作用类似于内存中的域对象集合。客户端对象以声明方式构造查询规范,并将它们提交给Repository以满足要求。可以从简单的对象集合中添加和删除对象,因为它们可以从简单的对象集合中删除,并且由存储库封装的映射代码将在后台执行适当的操作。从概念上讲,存储库封装了持久存储在数据存储中的对象集以及对它们执行的操作,从而提供了更加面向对象的持久层视图
存储库给出了基础数据的面向对象视图(可以以其他方式存储在关系数据库中)。它负责将您的表映射到您的实体。
为对象生成ID是完全不同的责任...您可以决定在数据库,域中或单独的服务中生成ID。无论ID在何处生成,存储库都应在您的实体和表之间无缝映射。
ID生成是它自己的责任,如果将其添加到存储库,那么您违反了单一责任原则。