Фабрики, сервисы, репозиторий в DDD

У меня есть несколько вопросов относительно фабрик, репозиториев и сервисов в DDD. У меня есть следующие объекты: Папка, файл, FileData.

По моему мнению, «Папка» является совокупным корнем и должна нести ответственность за создание объекта File и FileData.

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

public class FolderRepository : IFolderRepository
{
    #region Fields

    private readonly IFolderContext _context;
    private readonly IUnitOfWork _unitOfWork;

    #endregion

    #region Constructor

    public FolderRepository(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
        _context = _unitOfWork.Context as IFolderContext;
    }

    #endregion

    public IUnitOfWork UnitOfWork
    {
        get { return _unitOfWork; }
    }

    public IQueryable<Folder> All
    {
        get { return _context.Folders; }
    }

    public Folder Find(Guid id)
    {
        return _context.Folders.Find(id);
    }

    public void InsertGraph(Folder entity)
    {
        _context.Folders.Add(entity);
    }

    public void InsertOrUpdate(Folder entity)
    {
        if (entity.Id == Guid.Empty)
        {
            _context.SetAdd(entity);
        }
        else
        {
            _context.SetModified(entity);
        }
    }

    public bool Delete(Guid id)
    {
        var folder = this.Find(id) ?? _context.Folders.Find(id);
        _context.Folders.Remove(folder);

        return folder == null;
    }

    public int AmountOfFilesIncluded(Folder folder)
    {
        throw new NotImplementedException();
        //return folder.Files.Count();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Затем я создал службу на моем прикладном уровне, она называется "IoService". У меня есть сомнения по поводу расположения службы. Следует ли перенести его на уровень домена?

public class IoService : IIoService
{
    #region Fields

    private readonly IFolderRepository _folderRepository;
    private readonly IFileRepository _fileRepository;
    private readonly IUserReferenceRepository _userReferenceRepository;

    #endregion

    #region Constructor

    public IoService(IFolderRepository folderRepository, IFileRepository fileRepository, IUserReferenceRepository userReferenceRepository)
    {
        if(folderRepository == null)
            throw new NullReferenceException("folderRepository");
        if(fileRepository == null)
            throw new NullReferenceException("fileRepository");
        if (userReferenceRepository == null)
            throw new NullReferenceException("userReferenceRepository");

        _folderRepository = folderRepository;
        _fileRepository = fileRepository;
        _userReferenceRepository = userReferenceRepository;
    }

    #endregion

    #region Folder Methods

    /// <summary>
    /// Create a new 'Folder'
    /// </summary>
    /// <param name="userReference"></param>
    /// <param name="name"></param>
    /// <param name="parentFolder"></param>
    /// <param name="userIds">The given users represent who have access to the folder</param>
    /// <param name="keywords"></param>
    /// <param name="share"></param>
    public void AddFolder(UserReference userReference, string name, Folder parentFolder = null, IList<Guid> userIds = null, IEnumerable<string> keywords = null, bool share = false)
    {
        var userReferenceList = new List<UserReference> { userReference };

        if (userIds != null && userIds.Any())
        {
            userReferenceList.AddRange(userIds.Select(id => _userReferenceRepository.Find(id)));
        }

        var folder = new Folder
        {
            Name = name,
            ParentFolder = parentFolder,
            Shared = share,
            Deleted = false,
            CreatedBy = userReference,
            UserReferences = userReferenceList
        };

        if (keywords != null)
        {
            folder.Keywords = keywords.Select(keyword =>
                new Keyword
                {
                    Folder = folder,
                    Type = "web",
                    Value = keyword,
                }).ToList();
        }

        //insert into repository
        _folderRepository.InsertOrUpdate(folder);

        //save
        _folderRepository.UnitOfWork.Save();
    }

    /// <summary>
    /// Get 'Folder' by it's id
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public Folder GetFolder(Guid id)
    {
        return _folderRepository.Find(id);
    }

    #endregion

    #region File Methods

    /// <summary>
    /// Add a new 'File'
    /// </summary>
    /// <param name="userReference"></param>
    /// <param name="folder"></param>
    /// <param name="data"></param>
    /// <param name="name"></param>
    /// <param name="title"></param>
    /// <param name="keywords"></param>
    /// <param name="shared"></param>
    public void AddFile(UserReference userReference, Folder folder, FileData data, string name, string title = "", IEnumerable<string> keywords = null, bool shared = false)
    {
        var file = new File
        {
            Name = name,
            Folder = folder,
            FileData = data,
            CreatedBy = userReference,
            Type = data.Type
        };

        if (keywords != null)
        {
            file.Keywords = keywords.Select(keyword =>
                new Keyword
                {
                    File = file,
                    Type = "web",
                    Value = keyword,
                }).ToList();
        }

        folder.Files.Add(file);
        folder.Updated = DateTime.UtcNow;

        _folderRepository.InsertOrUpdate(folder);

        //save
        _folderRepository.UnitOfWork.Save();
    }

    /// <summary>
    /// Get 'File' by it's id
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public File GetFile(Guid id)
    {
        return _fileRepository.Find(id);
    }

    #endregion
}

Подводя итог: следует ли использовать службу для создания объекта папки. Или служба должна просто использовать фабрику, которая отвечает за создание объекта и отправку созданного объекта в репозиторий? Как насчет внедрения зависимостей в службу, должен ли я внедрять свои службы из уровня пользовательского интерфейса с контейнерами IOC, такими как Unity, или я должен просто жестко запрограммировать зависимости в службе?

Спасибо


person John    schedule 05.03.2013    source источник


Ответы (1)


Итак, мой первый вопрос: должен ли я использовать фабрику для создания этого агрегата или это зависит от репозитория?

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

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

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

У меня есть сомнения по поводу расположения службы. Должен ли он быть перемещен на доменный уровень?

ИМО, служба приложений может быть размещена на уровне предметной области, поскольку она уже служит фасадом, и их совместное использование принесет преимущества сплоченности. Одна мысль о IoService заключается в том, что такие методы, как AddFile, обычно параметризуются совокупными идентификаторами, а не экземплярами. Поскольку служба приложений уже ссылается на репозиторий, она может загружать соответствующие агрегаты по мере необходимости. В противном случае вызывающий код будет отвечать за вызов репозитория.

Должен ли я использовать службу для создания объекта папки. Или служба должна просто использовать фабрику, которая отвечает за создание объекта и отправку созданного объекта в репозиторий?

IoService выглядит хорошо, за исключением предыдущего комментария о параметризации идентификаторами, а не экземплярами.

Как насчет внедрения зависимостей в службу, следует ли мне внедрять свои службы из уровня пользовательского интерфейса с помощью контейнеров IOC, таких как Unity, или я должен просто жестко запрограммировать зависимости в службе?

Это вопрос предпочтений. Если вы можете извлечь выгоду из использования контейнера IoC, используйте его. Однако не используйте его только для того, чтобы использовать. Вы уже делаете внедрение зависимостей, просто без причудливого контейнера IoC.

ОБРАЗЕЦ

class File
{
    public File(string name, Folder folder, FileData data,  UserReference createdBy, IEnumerable<string> keywords = null)
    {
        //...
    }
}

...

class Folder
{
    public File AddFile(string name, FileData data, UserReference createdBy, IEnumerable<string> keywords = null)
    {
        var file = new File(name, this, data, createdBy, keywords)
        this.Files.Add(file);
        this.Updated = DateTime.UtcNow;
        return file;
    }
}

...

public void AddFile(UserReference userReference, Guid folderId, FileData data, string name, string title = "", IEnumerable<string> keywords = null, bool shared = false)
{
    var folder = _folderRepository.Find(folderId);
    if (folder == null)
        throw new Exception();

    folder.AddFile(name, data, keywords);

    _folderRepository.InsertOrUpdate(folder);
    _folderRepository.UnitOfWork.Save();
}

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

person eulerfx    schedule 05.03.2013
comment
Большое спасибо за ответ. Не могли бы вы привести краткий пример того, как бы вы написали метод AddFile, чтобы я на 100 % разобрался в этой теме. - person John; 05.03.2013
comment
О, круто, спасибо большое, теперь мне стало гораздо понятнее. - person John; 05.03.2013
comment
Итак, скажем, вы хотите получить файл по его идентификатору, вам нужно добавить метод в репозиторий папок для получения файла? - person John; 06.03.2013
comment
Инъекция зависимостей и, следовательно, возможное использование IoC действительно не представлены в вашем примере. Посмотрите на функцию addFile. Эта функция создает объект File. Например, в (модульных) тестах вы не можете обменять это на фиктивный класс с теми же свойствами. Вы по-прежнему тесно связываете определенные классы. При этом, пожалуйста, ознакомьтесь с принципом единой ответственности. Вы позволяете своей папке создавать файлы вместо того, что она должна делать (управлять коллекцией файлов внутри себя) - person mvbrakel; 27.11.2013
comment
@mvbrakel Я тоже слышал об этом, что меня смущает. Таким образом, файловый объект должен быть создан в методе AddFile IoService и позволить _fileRepository сохранить его до вызова folder.AddFile(), верно? - person Junyo; 03.07.2015