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

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

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

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;
    }
}

Если я попытаюсь создать дочерние элементы в factory, мне нужен доступ к репо. Если я сделаю это в Repo, я чувствую, что делаю работу, которая должна быть для factory. Не знаете, как справиться с этим.

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

Любая помощь будет очень оценена.

Ответ 1

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

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

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

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

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

Update

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

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

Ответ 2

Репозитории обрабатывают агрегированные корни, а не сущности. Итак, я предлагаю вам пойти с решением FooTree AR и получить его из db. Factory не должен зависеть от Репозитория, поэтому вам нужно захватить все данные дерева и передать их в factory. Кроме того, вы можете реализовать что-то вроде игры с ленивой загрузкой ORM и "ленивой загрузки", когда запрашивает их AR-клиент (или сам AR).

Ответ 3

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

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

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

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, чтобы заставить Foos инициировать спецификацию.

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