数据存储库组织

发布于 2024-09-26 02:22:50 字数 1545 浏览 4 评论 0原文

因此,我正在开发一些软件,并尝试让自己继续使用 TDD 和其他最佳实践。

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

假设我有以下类:CustomerOrderOrderLine

现在,我是否创建像这样的 Order

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

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

,它会序列化很好,但是,如果我不关心 OrderLinesCustomer 细节并不像人们想象的那么轻量。或者我只是将 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;
    }
}

这里的“最佳实践”是什么?

So, I'm developing some software, and trying to keep myself using TDD and other best practices.

I'm trying to write tests to define the classes and repository.

Let's say I have the classes, Customer, Order, OrderLine.

Now, do I create the Order class as something like

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

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

Which will serialize nice, but, if I don't care about the OrderLines, or Customer details is not as lightweight as one would like. Or do I just store IDs to items and add a function for getting them?

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

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

And how would you structure the repository for something like this?

Do I use an abstract CRUD repository with methods GetByID(int), Save(entity), Delete(entity) that each items repository inherits from, and adds it's own specific methods too, something like this?

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;
    }
}

What's the 'best practice' here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

三月梨花 2024-10-03 02:22:50

实体

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

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

Customer 也是一个聚合根。它不是订单的一部分,仅与订单相关。如果订单不再存在,那么客户也就不存在了。相反,如果客户不复存在,您将希望保留订单用于记账目的。由于 Customer 仅相关,因此您需要在订单中仅包含 CustomerId

class Order
{
  int OrderId { get; }

  int CustomerId { get; set; }

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

存储库

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

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 操作可能不同,这使得基本实现毫无用处。您确实要删除订单,还是只是将其状态设置为已删除?如果删除客户,相关订单会怎样?

我的建议是让事情变得简单。远离抽象和通用基类。当然,所有存储库都共享某种功能,并且泛型看起来很酷。但你真的需要它吗?

Entities

Let's start with the Order entity. An order is an autonomous object, which isn't dependent on a 'parent' object. In domain-driven design this is called an aggregate root; it is the root of the entire order aggregate. The order aggregate consists of the root and several child entities, which are the OrderLine entities in this case.

The aggregate root is responsible for managing the entire aggregate, including the lifetime of the child entities. Other components are not allowed to access the child entities; all changes to the aggregate must go through the root. Also, if the root ceases to exist, so do the children, i.e. order lines cannot exist without a parent order.

The Customer is also an aggregate root. It isn't part of an order, it's only related to an order. If an order ceases to exist, the customer doesn't. And the other way around, if a customer ceases to exist, you'll want to keep the orders for bookkeeping purposes. Because Customer is only related, you'll want to have just the CustomerId in the order.

class Order
{
  int OrderId { get; }

  int CustomerId { get; set; }

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

Repositories

The OrderRepository is responsible for loading the entire Order aggregate, or parts of it, depending on the requirements. It is not responsible for loading the customer. If you need the customer, load it from the CustomerRepository, using the CustomerId from the order.

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

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

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

If you ever need to load the order lines afterwards, you should use the tell, don't ask principle. Tell the order to load its order lines, and which repository to use. The order will then tell the repository the information it needs to know.

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);
  }
}

Note that the code uses an IOrderRepository to retrieve the order lines, rather than a separate repository for order lines. Domain-driven design states that there should be a repository for each aggregate root. Methods for retrieving child entities belong in the repository of the root and should only be accessed by the root.

Abstract/base repositories

I have written abstract repositories with CRUD operations myself, but I found that it didn't add any value. Abstraction is useful when you want to pass instances of subclasses around in your code. But what kind of code will accept any BaseRepository implementation as a parameter?

Also, the CRUD operations can differ per entity, making a base implementation useless. Do you really want to delete an order, or just set its status to deleted? If you delete a customer, what will happen to the related orders?

My advice is to keep things simple. Stay away from abstraction and generic base classes. Sure, all repositories share some kind of functionality and generics look cool. But do you actually need it?

旧城空念 2024-10-03 02:22:50

我会将我的项目分为相关的部分。数据传输对象(DTO)、数据访问对象(DAO)。我希望 DTO 尽可能简单,这里使用 POJO(普通旧式 Java 对象)和 POCO(普通旧式 C 对象)等术语,简单地说它们是容器对象,内置的功能很少(如果有的话)。

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 方法中。

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

然而,总是被忽视的一个难题是多用户环境,其中 DTO(普通对象)与应用程序断开连接并返回到 DAO 进行 CRUD。这通常涉及一些并发控制,这可能很麻烦并且可能变得复杂。在这里可以使用诸如日期时间或版本号之类的简单机制,尽管在对嵌套对象进行增删改查时,您必须制定有关更新内容和更新顺序的规则,而且如果更新失败并发,您必须决定是否失败全部或部分操作。

I would divide my project up into the relevant parts. Data Transfer Objects (DTO), Data Access Objects (DAO). The DTO's I would want to be as simple as possible, terms like POJO (Plain Old Java Object) and POCO (Plain Old C Object) are used here, simply put they are container objects with very little if any functionality built into them.

The DTO's are basically the building blocks to the whole application, and will marry up the layers. For every object that is modeled in the system, there should be at least one DTO. How you then put these into collections is entirely up to the design of the application. Obviously there are natural One to many relationships floating around, such as Customer has many Orders. But the fundamentals of these objects are what they are. For example, an order has a relationship with a customer, but can also be stand alone and so needs to be separate from the customer object. All Many to Many Relationships should be resolved down into One to Many relationships which is easy when dealing with nested classes.

Presumably there should be CRUD objects that appear within the Data Access Objects category. This is where it gets tricky as you have to manage all the relationships that have been discovered in design and the lifetime models of each. When fetching DTO's back from the DAO the loading options are essential as this can mean the difference between your system running like a dog from over eager loading, or high network traffic from fetching data back and fourth from your application and the store by lazy loading.

I won't go into flags and loading options as others here have done all that.

    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
    }

}

As you can see the OrderDTO is passed into the Create Method.

For the Create Method, when dealing with brand new nested Objects, there will have to be some code dealing with the marrying up of data that has been stored, for example a customer with old orders, and a new order. The system will have to deal with the fact that some of the operations are update statements, whilst others are Create.

However one piece of the puzzle that is always missed is that of multi-user environments where DTO's (plain Objects) are disconnected from the application and returned back to the DAO for CRUD. This usually involves some Concurrency Control which can be nasty and can get complicated. A simple mechanism such as DateTime or Version number works here, although when doing crud on a nested object, you must develop the rules on what gets updated and in what order, also if an update fails concurrency, you have to decide on whether you fail all the operation or partial.

往日情怀 2024-10-03 02:22:50

为什么不创建单独的 Order 类?在我看来,您正在描述一个基本订单对象,其中包含基本订单和客户信息(或者甚至可能不是客户信息),以及一个包含行项目的单独订单对象。

过去,我按照尼尔斯的建议做了,或者使用布尔标志或枚举来描述可选地加载子对象、列表等。在 干净的代码,Bob叔叔说这些变量和函数参数是程序员用来不将类或函数重构为更小、更容易理解的片段的借口。

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

Why not create separate Order classes? It sounds to me like you're describing a base Order object, which would contain the basic order and customer information (or maybe not even the customer information), and a separate Order object that has line items in it.

In the past, I've done as Niels suggested, and either used boolean flags or enums to describe optionally loading child objects, lists, etc. In Clean Code, Uncle Bob says that these variables and function parameters are excuses that programmers use to not refactor a class or function into smaller, easier to digest pieces.

As for your class design, I'd say that it depends. I assume that an Order could exist without any OrderLines, but could not exist without a Customer (or at least a way to reference the customer, like Niels suggested). If this is the case, why not create a base Order class and a second FullOrder class. Only FullOrder would contain the list of OrderLines. Following that thought, I'd create separate repositories to handle CRUD operations for Order and FullOrder.

小梨窩很甜 2024-10-03 02:22:50

如果您对 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

If you are interested in domain driven design (DDD) implementation with POCOs along with explanations take a look at the following 2 posts:

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

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

There is also a project that implements domain driven patterns (repository, unit of work, etc, etc) for various persistence frameworks (NHibernate, Entity Frameworks, etc, etc) called NCommon

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文