Хранилища, фабрики и иерархически структурированные данные


Я рассматриваю доменный дизайн Эрика Эванса, где он описывает взаимодействие между репозиториями и фабриками. Репозиторий сам вызовет интерфейс БД для получения результирующего набора. Этот результирующий набор затем будет передан на фабрику, которая будет понимать этот результирующий набор, чтобы воссоздать объект.

Что, если данные были иерархическими по своей природе, как какая-то древовидная структура? Например:

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Foo Parent { get; set; }
    public ICollection<Foo> { get; set; }

    // Other business like methods here

}

Используя DDD, я бы имел свои интерфейсы и реализации:

public interface IFooRepository
{
    Foo Get(int id);
}

public interface IFooFactory<TSource>
{
    Foo Create(TSource source);
}

public class SqlFooRepository: IFooRepository
{
    private readonly IFooDao dao;
    private readonly IFooFactory<SqlDataReader> factory;

    public SqlFooRepository(IFooDao dao, IFooFactory factory)
    {
        this.dao = dao;
        this.factory = factory;
    }

    public Foo Get(int id)
    {
        var resultSet = dao.Find(id);
        return factory.Create(resultSet);
    }
}

public class SqlFooFactory: IFooFactory<SqlDataReader>
{
    public Foo Get(SqlDataReader reader)
    {
        var foo = new Foo();
        foo.Id = (int) reader["id];
        foo.Name = (string) reader["name"];
            // Do I build the children accounts here
        return foo;
    }
}
Если я попытаюсь построить детей на фабрике, то мне снова понадобится доступ к РЕПО. Если я делаю это в репо, я чувствую, что делаю работу, которая должна быть для фабрики. Не знаю, как справиться с этим. Одна из моих мыслей заключается в том, что Foo-это не совокупный корень, а скорее FooTree-совокупный корень. Поэтому, пытаясь получить любой Foo, мне нужно было бы создать целое дерево, что означает, что я мог бы передать коллекцию объектов Foo в a FooTreeFactory. Любая помощь была бы очень признательна.
3 10

3 ответа:

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

При построении подобной иерархии необходимо убедиться, что вы попали в базу данных только один раз. Например, если бы вы принесли Foo foo1 в одном вызовите БД, а затем извлеките дочерние элементы foo1, используя тот же метод репозитория repo.Get(foo1.Id), тогда у вас будет дополнительный цикл БД для каждого дочернего элемента ... а потом еще несколько, если вы это тоже делаете рекурсивно для каждого ребенка. Вы не хотите этого, потому что это приведет к неизвестному числу дополнительных обходов базы данных (вариант задачи select N+1).

То, что вам нужно, - это репозиторий, который получает всю вашу иерархию в одной базе данных туда и обратно. Если вы используете ORM, то часто ORM имеет что-то встроенное, чтобы справиться с этим для вас; например NHibernate имеет DistinctRootEntityResultTransformer чтобы сделать именно это.

Если вы хотите сделать это с обычным репозиторием sql, то я бы создал хранимую процедуру (предполагая, что вы используете Sql Server), которая рекурсивно извлекает все Foo строки в иерархии из базы данных и возвращает их в один результирующий набор в репозиторий. Затем репозиторий передает этот результирующий набор на фабрику для создания объекта дерево.

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

обновить

Перечитав ваш вопрос, я думаю, что вы и @enrico попали в точку:

[...[9]} - это совокупный корень. Поэтому, пытаясь получить любой Foo, я бы нужно создать целое дерево, а это значит, что я могу передать коллекцию из Foo объекты в a FooTreeFactory

Репозитории обрабатывают агрегатные корни, а не сущности. Итак, я предлагаю вам пойти с решением FooTree AR и получить его из БД. Фабрика не должна зависеть от репозитория, поэтому вы должны захватить все данные дерева и передать их на фабрику. Кроме того, вы можете реализовать что-то вроде orm's lazy loading и "lazy load" children, когда AR-клиент (или сам AR) запрашивает их.

Не ссылайтесь на другие AggregateRoot при проектировании Aggregate.

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

Обновить
Вы можете рассмотреть шаблон спецификации , Если эта иерархия используется для деривации доменов.

public class FooBarSpecification
{
    public Foo Parent //injected by constructor
    public ICollection<Foo> //injected by constructor

    public boolean isSatisfiedBy(Foo foo) {
        //use FooTree here to
    }

}

Клиент может использовать FooRepository, чтобы заставить Foo инициировать спецификацию.

Другое решение - использование DomainService.