领域驱动设计中的工厂、服务和仓储

3
我有关于DDD中的工厂、仓储和服务方面的一些问题。我有以下实体:Folder、file、FileData。
在我看来,“Folder”是一个聚合根,并且应该负责创建文件和FileData对象。
所以我的第一个问题是,我应该使用工厂来创建这个聚合根,还是由仓储来完成?目前我有两个仓储,一个是为了Folder而存在的,另一个是为了File而存在的,但是我觉得它们应该合并成一个。下面的代码片段显示了我的Folder仓储,该仓储位于基础设施层中:
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
}

总结一下: 我应该使用创建文件夹对象的服务吗?还是服务只应该使用一个“工厂”,负责创建对象并将创建的对象发送到存储库中呢?在服务中,依赖注入应该怎么做?我应该像Unity那样使用IOC容器从UI层注入我的服务,还是我应该在服务中硬编码这些依赖项?
谢谢
1个回答

8
我的第一个问题是,我应该使用工厂来创建此聚合还是由存储库决定?
工厂负责创建,而存储库负责持久化。在重新构建时,存储库将有效地创建实例。然而,通常情况下,这个创建过程是通过反射完成的,并且不经过工厂,以防止仅应在创建时发生的初始化。
此时我有两个存储库,一个用于文件夹,另一个用于文件,但我认为我应该将它们合并。
在DDD中,您会为每个聚合拥有一个存储库。该存储库将负责持久化所有作为聚合部分的实体和值对象。
我对服务的位置有疑问。它应该移动到领域层吗?
在我看来,应用程序服务可以放置在领域层中,因为它已经充当了外观,并将它们放在一起会带来内聚性的好处。关于IoService的一个想法是,例如AddFile这样的方法通常会由聚合标识参数化,而不是实例。由于应用程序服务已经引用了存储库,它可以根据需要加载适当的聚合。否则,调用代码将负责调用存储库。
我应该使用服务来创建文件夹对象吗?还是服务只使用工厂,由工厂负责创建对象并将创建的对象发送到存储库?
IoService看起来很好,除了之前关于参数化身份而不是实例的评论。
那么在服务中进行依赖注入,我应该使用IOC容器(如Unity)从UI层注入我的服务,还是只需在服务中硬编码依赖项?
这是个人喜好问题。如果您可以从使用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();
}

在这个例子中,更多的行为被委托给了文件夹聚合和文件实体。应用程序服务仅在聚合上调用适当的方法。

非常感谢您的回复。您能否给我一个简短的例子,展示一下如何编写AddFile方法,这样我就可以完全明白这个话题了。 - John
哦,太酷了,非常感谢,现在我明白得多了。 - John
假设你想通过文件ID获取文件,那么你需要在文件夹存储库中添加一个方法来接收该文件。 - John
依赖注入以及可能使用的IoC在您的示例中并没有得到很好的体现。请看addFile函数。该函数创建一个文件对象。例如,在(单元)测试中,您无法将其替换为具有相同属性的模拟类。您仍然紧密耦合某些类。因此,请查看单一职责原则。您让您的文件夹创建文件,而不是它应该做的事情(在自身内部管理文件集合)。 - mvbrakel
@mvbrakel 我也听说过这个,让我有点困惑。所以文件对象应该在 IoService 的 AddFile 方法中被实例化,然后由 _fileRepository 存储,直到调用 folder.AddFile(),对吧? - Junyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接