数据存储库组织

时间:2010-10-05 21:51:32

标签: language-agnostic repository-pattern

所以,我正在开发一些软件,并试图让自己使用TDD和其他最佳实践。

我正在尝试编写测试来定义类和存储库。

假设我有课程CustomerOrderOrderLine

现在,我要将Order类创建为类似

的类
abstract class Entity {
    int ID { get; set; }
}

class Order : Entity {
    Customer Customer { get; set; }
    List<OrderLine> OrderLines { get; set; }
}

哪个序列化很好,但是,如果我不关心OrderLines,或Customer细节不像人们想要的那样轻量级。或者我只是将ID存储到项目中并添加一个函数来获取它们?

class Order : Entity {
    int CustomerID { get; set; }
    List<OrderLine> GetOrderLines() {};
}

class OrderLine : Entity {
    int OrderID { get; set; }
}

你会如何为这样的东西构建存储库?

我是否使用抽象的CRUD存储库,其中包含每个项目存储库继承的方法GetByID(int)Save(entity)Delete(entity),并添加它自己的特定方法,类似这样的东西?

public abstract class RepositoryBase<T, TID> : IRepository<T, TID> where T : AEntity<TID>
{
    private static List<T> Entities { get; set; }

    public RepositoryBase()
    {
        Entities = new List<T>();
    }

    public T GetByID(TID id)
    {
        return Entities.Where(x => x.Id.Equals(id)).SingleOrDefault();
    }

    public T Save(T entity)
    {
        Entities.RemoveAll(x => x.Id.Equals(entity.Id));
        Entities.Add(entity);
        return entity;
    }

    public T Delete(T entity)
    {
        Entities.RemoveAll(x => x.Id.Equals(entity.Id));
        return entity;
    }
}

这里的“最佳做法”是什么?

4 个答案:

答案 0 :(得分:2)

实体

让我们从Order实体开始。订单是一个自治对象,它不依赖于“父”对象。在域驱动设计中,这称为聚合根;它是整个订单汇总的根源。订单聚合由根和几个子实体组成,在这种情况下是OrderLine个实体。

聚合根负责管理整个聚合,包括子实体的生命周期。其他组件不允许访问子实体;对聚合的所有更改都必须通过根。此外,如果根不再存在,那么子节点也就不存在了,即没有父订单就不能存在订单行。

Customer也是聚合根。它不是订单的一部分,它只与订单有关。如果订单不再存在,则客户不会。反过来说,如果客户不再存在,您将需要保留订单以便进行簿记。由于Customer只是相关的,因此您只需要订单中的CustomerId

class Order
{
  int OrderId { get; }

  int CustomerId { get; set; }

  IEnumerable<OrderLine> OrderLines { get; private set; }
}

存储库

OrderRepository负责加载整个Order聚合或部分聚合,具体取决于要求。它不负责加载客户。如果您需要客户,请使用订单中的CustomerRepositoryCustomerId加载客户。

class OrderRepository
{
  Order GetById(int orderId)
  {
    // implementation details
  }

  Order GetById(int orderId, OrderLoadOptions loadOptions)
  {
    // implementation details
  }
}

enum OrderLoadOptions
{
  All,
  ExcludeOrderLines,
  // other options
}

如果您之后需要加载订单行,则应使用告诉,不要问原则。告诉订单加载其订单行,以及要使用的存储库。然后,订单将告诉存储库它需要知道的信息。

class Order
{
  int OrderId { get; }

  int CustomerId { get; set; }

  IEnumerable<OrderLine> OrderLines { get; private set; }

  void LoadOrderLines(IOrderRepository orderRepository)
  {
    // simplified implementation
    this.OrderLines = orderRepository.GetOrderLines(this.OrderId);
  }
}

请注意,代码使用IOrderRepository来检索订单行,而不是订单行的单独存储库。域驱动设计指出每个聚合根应该有一个存储库。检索子实体的方法属于根的存储库,只能由根访问。

抽象/基础存储库

我自己编写了带有CRUD操作的抽象存储库,但我发现它没有添加任何值。想要在代码中传递子类的实例时,抽象很有用。但是什么样的代码会接受任何BaseRepository实现作为参数?

此外,CRUD操作可能因实体而异,使基本实现无用。您真的要删除订单,还是仅将其状态设置为已删除?如果删除客户,相关订单会发生什么?

我的建议是保持简单。远离抽象和通用基类。当然,所有存储库都具有某种功能,而且泛型看起来很酷。但你真的需要吗?

答案 1 :(得分:1)

我会将我的项目分成相关部分。数据传输对象(DTO),数据访问对象(DAO)。 DTO我希望尽可能简单,这里使用像POJO(Plain Old Java Object)和POCO(Plain Old C Object)这样的术语,简单地说它们是容器对象,如果有任何功能,它们内置很少。 / p>

DTO基本上是整个应用程序的构建块,并将层层结构。对于在系统中建模的每个对象,应至少有一个DTO。然后,如何将这些放入集合中完全取决于应用程序的设计。显然有一种自然的一对多关系,例如客户有很多订单。但这些对象的基本原理就是它们。例如,订单与客户有关系,但也可以是独立的,因此需要与客户对象分开。应该将所有多对多关系解析为一对多关系,这在处理嵌套类时很容易。

据推测,应该有数据访问对象类别中出现的CRUD对象。这是一个棘手的问题,因为您必须管理在设计中发现的所有关系以及每个关系的生命周期模型。从DAO获取DTO的时候,加载选项是必不可少的,因为这可能意味着您的系统像过度加载的狗一样运行之间的差异,或者通过延迟加载从您的应用程序和商店中取回数据和第四次的高网络流量。

我不会进入旗帜并加载选项,因为其他人已经完成了所有这些。

    class OrderDAO
    {
    public OrderDTO Create(IOrderDTO order)
    {
    //Code here that will create the actual order and store it, updating the 
flelds in the OrderDTO where necessary. One being the GUID field of the new ID. 
I stress guid as this means for better scalability.

    return OrderDTO
    }

}

正如您所见,OrderDTO已传递到Create Method。

对于创建方法,在处理全新的嵌套对象时,必须有一些代码处理已存储的数据的结合,例如具有旧订单的客户和新订单。系统必须处理一些操作是更新语句的事实,而其他操作是Create。

然而,总是遗漏的一个难题是多用户环境,其中DTO(普通对象)与应用程序断开连接并返回到CRO的DAO。这通常涉及一些并发控制,这可能是令人讨厌的并且可能变得复杂。一个简单的机制,例如DateTime或Version number在这里工作,虽然在嵌套对象上做crud时,你必须根据更新的内容和顺序开发规则,如果更新失败并发,你必须决定是否失败所有操作或部分操作。

答案 2 :(得分:0)

为什么不创建单独的Order类?听起来像你正在描述一个基本订单对象,它包含基本订单和客户信息(或者甚至可能不包括客户信息),以及一个单独的订单对象,其中包含订单项。

在过去,我已经按照Niels的建议完成了,并使用布尔标志或枚举来描述可选择加载子对象,列表等。在Clean Code中,Bob叔叔说这些变量和函数参数是程序员用来不将类或函数重构为更小,更容易消化的部分的借口。

至于你的班级设计,我会说这取决于你。我假设一个订单可以在没有任何OrderLines的情况下存在,但是如果没有客户(或者至少是一种引用客户的方式,就像Niels建议的那样)就不存在。如果是这种情况,为什么不创建基本Order类和第二个FullOrder类。只有FullOrder才会包含OrderLines列表。按照这个想法,我将创建单独的存储库来处理Order和FullOrder的CRUD操作。

答案 3 :(得分:0)

如果您对使用POCO进行域驱动设计(DDD)实施感兴趣以及解释,请查看以下2个帖子:

http://devtalk.dk/2009/06/09/Entity+Framework+40+Beta+1+POCO+ObjectSet+Repository+And+UnitOfWork.aspx

http://www.primaryobjects.com/CMS/Article122.aspx

还有一个项目为各种持久性框架(NHibernate,实体框架等)实现域驱动模式(存储库,工作单元等),称为NCommon