Как должен быть структурирован уровень доступа к данным? - PullRequest
10 голосов
/ 28 сентября 2008

Я изначально проектировал свою систему, следуя примеру архитектуры s # , описанному в этой статье codeproject (К сожалению, я не использую NHibernate). Основная идея заключается в том, что для каждого объекта домена, который должен был бы взаимодействовать со слоем постоянства, у вас должен быть соответствующий объект доступа к данным в другой библиотеке. Каждый объект доступа к данным реализует интерфейс, и, когда объекту домена требуется доступ к методу доступа к данным, он всегда кодирует для интерфейса, а не для самих DAO.

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

Интересно, что многие из этих DAO (и их соответствующие интерфейсы) являются очень простыми существами - самый распространенный имеет только один метод GetById (). Я получаю целую кучу объектов, таких как

public interface ICustomerDao {
  Customer GetById(int id);
}

public interface IProductDao {
  Product GetById(int id);
}
public interface IAutomaticWeaselDao {
  AutomaticWeasel GetById(int id);
}

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

public interface SimpleObjectRepository {
      Customer GetCustomerById(int id);
      Product GetProductById(int id);
      AutomaticWeasel GetAutomaticWeaselById(int id);
      Transaction GetTransactioinById(int id);
}
public interface TransactionDao {
  Transaction[] GetAllCurrentlyOngoingTransactionsInitiatedByASweatyGuyNamedCarl();
}

Кто-нибудь имеет опыт работы с такой архитектурой? В целом, я очень доволен настройкой, так как теперь моей единственной заботой является управление всеми этими небольшими файлами. Однако мне все еще интересно, какие существуют другие подходы к структурированию уровня доступа к данным.

Ответы [ 5 ]

3 голосов
/ 25 октября 2008

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

Таким образом, мой подход состоит в том, чтобы иметь репозиторий для каждого агрегата, который в этом нуждается, например CustomerRepository. Это будет метод Add (сохранить) и, если подходит для этого агрегата, метод Remove (удаление). У него также могут быть любые другие пользовательские методы, включая запросы (GetActive), и, возможно, некоторые из этих запросов могут принимать спецификации.

Это звучит как много усилий, но кроме пользовательских запросов большая часть кода, по крайней мере, если вы используете современный ORM, очень проста в реализации, поэтому я использую наследование (ReadWriteRepositoryBase, где T: IAggregateRoot) и / или состав (обращение к классу RepositoryHelper). Базовый класс может иметь методы, которые применяются во всех случаях, например, GetById.

Надеюсь, это поможет.

2 голосов
/ 28 сентября 2008

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

interface DataAccessObject
{
public static function get(array $filters = array(), array $order = array(), array $limit = array());
public function insert();
public function update();   
public function delete();
}

И тогда каждый из моих объектов доступа к данным работает примерно так:

class DataAccessObject implements DataAccessObject
{
    public function __construct($dao_id = null) // So you can construct an empty object
    {
        // Some Code that get the values from the database and assigns them as properties
    }
    public static function get(array $filters = array(), array $order = array(), array $limit = array()) {};  // Code to implement function
    public function insert() {};  // Code to implement function
    public function update() {};  // Code to implement function 
    public function delete() {};  // Code to implement function        
}

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

Однако вы также можете использовать метаданные SQL (при условии, что у вас есть достаточно надежный дизайн базы данных, использующий преимущества ограничений внешнего ключа и т. П.) Для создания этих объектов доступа к данным. Тогда теоретически вы могли бы использовать один родительский класс DataAccessObject для создания свойств и методов класса и даже для автоматического построения отношений с другими таблицами в базе данных. Это более или менее выполнит то же самое, что вы описываете, потому что тогда вы можете расширить класс DataAccessObject для предоставления пользовательских методов и свойств для ситуаций, которые требуют некоторого количества кода, созданного вручную.

Являясь знаком для разработки .NET, вы рассматривали платформу, которая обрабатывает базовую структуру уровня доступа к данным, например, Subsonic? Если нет, я бы порекомендовал изучить именно такие рамки: http://subsonicproject.com/.

Или для разработки на PHP, фреймворк, такой как Zend Framework, предоставит аналогичную функциональность: http://framework.zend.com

2 голосов
/ 29 сентября 2008

Джордж, я точно знаю, что ты чувствуешь. Архитектура Билли имеет для меня смысл, но необходимость создания контейнера, файлов Imapper и mapper является болезненной. Затем, если вы используете NHibernate, соответствующий файл .hbm и, как правило, несколько сценариев модульного тестирования, чтобы проверить все, что работает.

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

public class BaseDAO<T> : IDAO<T> 
{
     public T Save(T entity)
     { 
       //etc......
     }
}
public class YourDAO : BaseDAO<YourEntity>
{
}

Полагаю, что без NHibernate вы бы использовали отражение или какой-то другой механизм для определения того, какой SQL / SPROC вызывать?

В любом случае, я думал об этом, когда DAO нужно только выполнять базовые операции CRUD, определенные в базовом классе, тогда не должно быть необходимости писать собственные преобразователи и интерфейсы. Единственный способ добиться этого - использовать Reflection.Emit для динамического создания DAO на лету.

1 голос
/ 28 сентября 2008

Я также использую шаблон репозитория для своего DAO, и я вполне доволен им. Да, вы заканчиваете довольно маленькими классами, но это очень ремонтопригодно. Это немного мощнее, если вы используете интерфейс IQueriable (LINQ.). Вы также можете использовать дженерики (что-то вроде T GetById<T>(int id)), если у вас достаточно согласованная структура базы данных.

0 голосов
/ 29 сентября 2008

Основным недостатком второго подхода является модульное тестирование: создание больших фабричных методов, таких как SimpleObjectRepository, требует больше работы, чем просто ICustomerDao. Но мой проект идет со вторым подходом для сильно связанных объектов (где большинство будет использоваться в любых юнит-тестах), потому что он ослабляет умственную нагрузку и делает его более ремонтопригодным.

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

...