Шаблон репозитория: Что такое «правильный размер»? - PullRequest
14 голосов
/ 24 февраля 2009

Я создаю несколько репозиториев для приложения MVC и пытаюсь найти правильный способ разделения обязанностей между репозиториями. В большинстве случаев это очевидно. Но есть один конкретный случай, когда я не уверен, какой правильный ответ.

Пользователи этого приложения должны отслеживать несколько типов времени для своих сотрудников. Для простоты давайте рассмотрим только два. Я назову их «тайм-карты» и «посещаемость». Точный характер различия между этими двумя не очень важен, но вы должны заметить, что конечные пользователи считают их совершенно отдельными данными. Я думаю, однако, что причина, по которой они считают их полностью отдельными данными, заключается в том, что у них никогда не было возможности увидеть их вместе в прошлом. Оба типа записей имеют почти совершенно разные бизнес-правила, касающиеся редактирования записей, но они также, вообще говоря, обе записи о том, где сотрудник находился в определенное время. Оба типа записей времени имеют много общих свойств, таких как общее количество часов и сотрудник, для которого было собрано время. Оба типа также имеют несколько свойств, которые полностью уникальны для отдельного типа. Мы сохраняем эти «дополнительные» свойства в экземпляре другого типа. Итак, общая структура выглядит следующим образом:

class TimeRecord 
{ 
    Person Employee { get; set; }
    TimeSpan? Hours { get; set; }
}

class TimeCardData
{
     TimeRecord Record { get; set; }
     TProperty TimeCardProperty  { get; set; }
}

class AttendanceData
{
     TimeRecord Record { get; set; }
     TProperty AttendanceProperty  { get; set; }
}

Итак, вопрос в том, Сколько здесь требуется репозиториев?

1 Хранилище

В проекте только с одним репозиторием будут доступны методы для возврата «временных карт», «записей о посещаемости» или обоих типов в одном списке. Это довольно удобно для клиентов репозитория, но, на мой взгляд, есть реальная опасность стать очень толстым классом. Я думаю, что хранилище только для «временных карт» уже станет одним из самых больших хранилищ в системе, даже без учета «посещаемости» просто из-за сложных бизнес-правил.

2 Хранилища

В другом проекте был бы один репозиторий для «карточек времени» и другой репозиторий для записей «посещаемости». Это имеет то преимущество, что бизнес-правила, например, для «временных карт» находятся в одном месте. Но я также хотел бы иметь возможность получить список всех записей времени, независимо от типа. Не ясно, какой репозиторий использовать для этого случая. И

3 Хранилища

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

Hybrid

Гибридный подход будет использовать один репозиторий, но перенести любой код бизнес-правил (включая выбор записей) на отдельные типы. В этом примере один «репозиторий записей времени» будет агрегировать экземпляры классов реализации бизнес-правил для времени «карты времени» и «посещаемости». Я думаю, что именно такой подход я сейчас и поддерживаю.

Другое

Что-нибудь, что я пропустил? Есть ли веские аргументы в пользу одного дизайна над другим?

1 Ответ

18 голосов
/ 25 февраля 2009
  1. Хранилища, по крайней мере, насколько мне известно, являются местом для бизнес-правил. Это просто фасад, имитирующий коллекцию; под ними в основном чистый доступ к данным (если это их работа, вы, возможно, не сохраняете ничего и с репозиторием). Поэтому не следует рассматривать отдельные репозитории по причинам «бизнес-правил».

  2. Если ваши доменные объекты действительно являются отдельными объектами, то у вас должны быть отдельные репозитории. Помните, что такое хранилище: это фасад. Он имитирует коллекцию в вашем домене. Смотрите здесь для действительно хорошего сообщения в блоге на Репозитории: http://devlicio.us/blogs/casey/archive/2009/02/20/ddd-the-repository-pattern.aspx

Хранилище является фасадом; абстракция.

Тем не менее ... Я не думаю, что у вас есть отдельные объекты. У вас есть некоторые проблемы, которые не имеют ничего общего с репозиториями и все, что связано с доменом и дизайном домена. Являются ли два типа «временных карт» на самом деле две разные вещи, или они действительно одинаковы?

Вы говорите, «Но мне немного странно, что я могу получить одну и ту же запись из двух разных репозиториев».

Это говорит мне о том, что на самом деле это одинаковые данные, выраженные по-разному. И есть способы справиться с этим.

Если это действительно так, то у вас есть подклассы общего базового класса (что-то, что может быть легко смоделировано в БД и, например, элегантно обработано с помощью NHibernate).

Я приведу вам пример проекта, над которым я работаю. У меня есть то, что называется «Трансляция». Это базовый класс; Аннотация. Не может быть создан. У меня есть два конкретных конкретных типа этого класса: DeviceBroadcast и FileBroadcast. Один передает аудио / видео с устройства (например, карты захвата DirectX), а другой - аудио / видео с источника файла (например, .mp3).

У меня есть один репозиторий, который возвращает объект Broadcast. Я могу привести его к FileBroadcast, чтобы манипулировать конкретной информацией о FileBroadcast, или я могу привести его к DeviceBroadcast по той же причине - если он такого типа. Broadcast не может быть одновременно FileBroadcast и DeviceBroadcast. Это должен быть один или другой.

В базе данных я сохраняю общие параметры вещания в таблице Broadcast, а затем сохраняю специфические свойства файла в таблице FileBroadcast. То же самое касается таблицы DeviceBroadcast; отдельный. Однако когда я делаю запрос через репозиторий, я просто хочу трансляции. Это мой корневой агрегатный объект и, следовательно, мой репозиторий.

Базовый класс Broadcast имеет общие методы, которые используют оба подкласса (например, метод GetCommand (), который возвращает определенный аргумент командной строки для запуска процесса VLC). Подклассы должны переопределить и реализовать этот метод, потому что он абстрактный. Таким образом, «бизнес-логика», которая уникальна для FileBroadcast, содержится в классе FileBroadcast. «Бизнес-логика», уникальная для DeviceBroadcast, содержится в классе DeviceBroadcast. Любая общая для обоих логика содержится в суперклассе Broadcast.

Кажется, у вас похожая ситуация, и поэтому я делюсь своим дизайном. Я думаю, что это может послужить вам хорошо.

Прежде всего, подумайте о своем домене и данных. Если вы собираетесь получать дубликаты данных с помощью отдельных репозиториев, вам нужно больше думать о том, как вы разрабатываете домен. Не позволяйте пользователям диктовать дизайн вашего домена. Они знают область с их точки зрения. Все, что вам нужно сделать, - это предоставить им данные так, как они понимают. Это не значит, что у вас должен быть плохой дизайн; У вас может быть хороший дизайн за кулисами, потому что ваш код - это то, что должно использовать домен.

...