Как должен быть структурирован уровень доступа к данным?

StackOverflow https://stackoverflow.com/questions/144503

  •  02-07-2019
  •  | 
  •  

Вопрос

Изначально я разработал свою систему, следуя примеру архитектуры s#. описано в этой статье о кодовом проекте (К сожалению, я не использую 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();
}

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

Это было полезно?

Решение

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

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

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

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

Другие советы

Я работаю на 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

Джордж, я точно знаю, что ты чувствуешь.Архитектура Билли мне понятна, но необходимость создания контейнера, Imapper и файлов сопоставления болезненна.Затем, если вы используете 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 «на лету».

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top