Организация хранилища данных - PullRequest
3 голосов
/ 06 октября 2010

Итак, я занимаюсь разработкой программного обеспечения и пытаюсь использовать TDD и другие передовые методы.

Я пытаюсь написать тесты для определения классов и хранилища.

Допустим, у меня есть классы, Customer, Order, OrderLine.

Теперь я должен создать класс Order как что-то вроде

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

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

Это будет хорошо сериализовано, но, если мне плевать на детали OrderLines или Customer, они не так легки, как хотелось бы. Или я просто храню идентификаторы для предметов и добавляю функцию для их получения?

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 ]

2 голосов
/ 18 октября 2010

Сущности

Давайте начнем с сущности Order.Порядок - это автономный объект, который не зависит от «родительского» объекта.В доменном дизайне это называется агрегатный корень ;это корень всей совокупности заказов.Агрегат заказа состоит из корня и нескольких дочерних объектов, которые в данном случае являются OrderLine сущностями.

Корень агрегата отвечает за управление всей совокупностью, включая время жизни дочерних сущностей.Другие компоненты не имеют доступа к дочерним объектам;все изменения в совокупности должны проходить через корень.Кроме того, если корень перестает существовать, то и дочерние элементы, то есть строки заказа не могут существовать без родительского заказа.

Customer также является совокупным корнем.Это не часть заказа, это связано только с заказом.Если заказ перестает существовать, клиент не делает.И наоборот, если клиент перестает существовать, вы захотите сохранить заказы для целей бухгалтерского учета.Поскольку Customer относится только к вам, вы должны иметь только CustomerId в заказе.

class Order
{
  int OrderId { get; }

  int CustomerId { get; set; }

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

Хранилища

OrderRepository отвечает за загрузку всего Order агрегат или его части, в зависимости от требований.Он не несет ответственности за загрузку клиента.Если вам нужен клиент, загрузите его из CustomerRepository, используя CustomerId из заказа.

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 голос
/ 22 октября 2010

Я бы разделил свой проект на соответствующие части.Объекты передачи данных (DTO), Объекты доступа к данным (DAO).DTO, которые я хотел бы быть настолько простыми, насколько это возможно, здесь используются такие термины, как POJO (Простой старый Java-объект) и POCO (Простой старый C-объект), проще говоря, они являются объектами-контейнерами с очень малой функциональностью, встроенной в них.

DTO в основном являются строительными блоками для всего приложения и объединят слои.Для каждого объекта, который моделируется в системе, должен быть хотя бы один DTO.То, как вы затем поместите их в коллекции, полностью зависит от дизайна приложения.Очевидно, существуют естественные отношения Один-ко-многим, например, у Клиента много заказов.Но основы этих объектов являются такими, какие они есть.Например, заказ имеет отношения с клиентом, но также может быть отдельным и поэтому должен быть отделен от объекта клиента.Отношения «все ко многим» должны быть преобразованы в отношения «один ко многим», что легко при работе с вложенными классами.

Предположительно, должны быть объекты CRUD, которые появляются в категории «Объекты доступа к данным».Вот где это становится сложным, так как вам приходится управлять всеми взаимосвязями, которые были обнаружены в дизайне, и моделями жизни каждого из них.При извлечении данных DTO из DAO параметры загрузки имеют важное значение, так как это может означать разницу между вашей системой, работающей как собака, из-за чрезмерной загрузки, или большим сетевым трафиком из-за извлечения данных назад и в-четвертых из вашего приложения и магазина из-за отложенной загрузки.

Я не буду вдаваться в флаги и параметры загрузки, поскольку все остальные здесь сделали все это.

    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, когда речь идет о совершенно новых вложенных объектах, должен быть некоторый код, касающийся объединения данных, которые были сохранены, например, клиент со старыми заказами и новый заказ.Системе придется иметь дело с тем фактом, что некоторые операции являются операторами обновления, а другие являются Create.

Однако одна часть головоломки, которую всегда упускают, - это многопользовательская среда, в которой DTO (простой)Объекты) отключаются от приложения и возвращаются обратно в DAO для CRUD.Обычно это включает в себя некоторый контроль параллелизма, который может быть неприятным и усложненным.Здесь работает простой механизм, такой как DateTime или номер версии, хотя при выполнении crud для вложенного объекта вы должны разработать правила для того, что обновляется и в каком порядке, а также, если обновление не удается выполнить одновременно, вы должны решить, потерпели ли вы неудачувся операция или частичная.

0 голосов
/ 22 октября 2010

Если вы интересуетесь реализацией доменного проектирования (DDD) с POCO вместе с пояснениями, взгляните на следующие 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, Entity Frameworks и т. Д. И т. Д.), Называемый NCommon

0 голосов
/ 21 октября 2010

Почему бы не создать отдельные классы Order?Мне кажется, что вы описываете базовый объект «Заказ», который будет содержать основной заказ и информацию о клиенте (или, возможно, даже не информацию о клиенте), а также отдельный объект «Заказ», в котором есть позиции.

В прошлом я делал так, как предлагал Нильс, и использовал либо логические флаги, либо перечисления для описания необязательной загрузки дочерних объектов, списков и т. Д. В Чистый код дядя Боб говорит, что эти переменные и параметры функцииэто оправдания, которые программисты используют для того, чтобы не преобразовывать класс или функцию в более мелкие, более простые для усвоения части.

Что касается вашего класса, я бы сказал, что это зависит.Я предполагаю, что Заказ может существовать без каких-либо Линий заказов, но не может существовать без Клиента (или, по крайней мере, способа ссылки на него, как предложил Нильс).Если это так, почему бы не создать базовый класс Order и второй класс FullOrder.Только FullOrder будет содержать список OrderLines.После этой мысли я бы создал отдельные репозитории для обработки CRUD-операций для Order и FullOrder.

...