DDD 和文件管理

Erp*_*erp 8 c# events domain-driven-design cqrs asp.net-core

我正在组装一个简单的文档管理系统,我试图遵循可靠的 DDD 原则,一切都在顺利进行。我一直质疑的一个领域是管理文件的最干净的解决方案是什么。

作为背景,许多文档管理将围绕上传文档并将其分配给特定的“工作订单”。这是在制造业中,我们需要跟踪某些文件,并在完成客户的产品后将其发送给客户。

因此,我的有界上下文主要由几个主要实体组成,例如 DocumentPackage、文档、需求等。DocumentPackage 是单个“工作订单”的文档分组。同一文档可以在多个 DocumentPackages 中使用。每个 DocumentPackage 都有一定数量的需求,这是作为包的一部分所需的不同类型的文档。

因此,当涉及到上传和下载文件、操作文件以及更新数据库以反映这些更改时,我最想在哪里处理大部分操作?

这是我拥有的 UploadDocumentCommand 和处理程序的示例。请注意,我决定将上传的文件保存到 API 控制器中的本地文件系统,并将其作为 FileInfo 传递到我的命令中。

public class UploadDocumentCommand : IRequest<AppResponse>
{
    public UploadDocumentCommand(Guid documentId, string workOrderNumber, FileInfo file, Guid? requirementId = null)
    {
        DocumentId = documentId;
        WorkOrderNumber = new WorkOrderNumber(workOrderNumber);
        FileInfo = file;
        RequirementId = requirementId;
    }

    public Guid DocumentId { get; }
    public WorkOrderNumber WorkOrderNumber { get; }
    public Guid? RequirementId { get; }
    public FileInfo FileInfo { get; }
}

public class UploadDocumentCommandHandler : IRequestHandler<UploadDocumentCommand, AppResponse>
{
    private readonly IDocumentRepository documentRepository;
    private readonly IDocumentPackageRepository packageRepo;

    public UploadDocumentCommandHandler(IDocumentRepository documentRepository, IDocumentPackageRepository packageRepo)
    {
        this.documentRepository = documentRepository;
        this.packageRepo = packageRepo;
    }
    public async Task<AppResponse> Handle(UploadDocumentCommand request, CancellationToken cancellationToken)
    {
        try
        {
            // get the package, throws not found exception if does not exist
            var package = await packageRepo.Get(request.WorkOrderNumber);

            var document = DocumentFactory.CreateFromFile(request.DocumentId, request.FileInfo);

            if (request.RequirementId != null)
            {
                package.AssignDocumentToRequirement(document, request.RequirementId.GetValueOrDefault());
            } 
            else
            {
                package.AddUnassignedDocument(document);
            }

            await documentRepository.Add(document, request.FileInfo);
            await packageRepo.Save(package);

            return AppResponse.Ok();
        }
        catch (AppException ex)
        {
            // the file may have been uploaded to docuware but failed afterwards, this should be addressed
            // this can be better handled by using an event to add the document to the package only after successful upload
            return AppResponse.Exception(ex); 
        }

    }
}

public class Document : Entity<Guid>
{
    private Document() { }
    public Document(Guid id, string fileName, DateTime addedOn)
    {
        Id = id;
        FileName = new FileName(fileName);
        AddedOn = addedOn;
    }
    public FileName FileName { get; private set; }
    public DateTime AddedOn { get; private set; }

    public override string ToString() => $"Document {Id} {FileName}";
}
Run Code Online (Sandbox Code Playgroud)

我的 DocumentRepository 具有混合职责,我让它既将文件保存到文件存储又更新数据库。我现在正在使用一个特定的文档存储应用程序,但我想保持这个抽象,这样我就不会陷入困境。不同的文件(例如图像)也可能有不同的存储。但我的一部分想知道在我的应用程序层中使用此逻辑是否实际上更好,我的处理程序负责存储文件和更新数据库。我不认为 DocumentRepository 非常 SRP,并且加载文档实体的行为不应该依赖于我的 DocuwareRepository。

public class DocumentRepository : IDocumentRepository
{
    private readonly DbContext context;
    private readonly IDocuwareRepository docuwareRepository;

    public DocumentRepository(DbContext context, IDocuwareRepository docuwareRepository)
    {
        this.context = context;
        this.docuwareRepository = docuwareRepository;
    }
    public async Task<Document> Get(Guid id)
    {
        return await 
            context
            .Document
            .FirstOrDefaultAsync(x => x.Id.Equals(id));
    }

    public async Task Add(Document document, FileInfo fileInfo)
    {
        var results = await docuwareRepository.UploadToDocuware(document.Id, fileInfo);


        var storageInfo = new DocumentStorageInfo
        {
            DocuwareId = results.DocuwareDocumentId,
            DocuwareFileCabinetId = results.CabinetId,
            Document = document

        };

        context.DocumentStorageInfo.Add(storageInfo);
        context.Document.Add(document);

        await context.SaveChangesAsync();
    }

    public Task<FileStream> Download(Guid id)
    {
        throw new NotImplementedException();
    }

    public void Dispose()
    {
        context?.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在处理另一个需要下载 DocumentPackage 的用例。我希望我的应用程序从包中获取所有有效文档,将它们编译成 zip 文件,其中文档根据要求构建在文件夹层次结构中,并且出于可追溯性原因,该存档 zip 将长期保存,客户端可以下载该 zip 文件。所以我有另一个实体,我称之为 DocumentPackageArchive,它有一个存储库,我认为 zip 文件会在那里编译。这将要求存储库从各自的存储中下载所有文件,将其压缩为 zip,将 zip 本地保存在 Web 服务器上,将 zip 文件发送回以进行只读保存,并使用一些信息更新数据库有关存档的数据。是的,我有意创建文档包的副本作为快照。

因此,我觉得文件管理无处不在。我正在 Web api 中保存一些文件,因为我觉得当我从 IFormFile 获取它们时,我需要将它们保存到临时存储中。我将应用程序层中的文件信息作为命令的一部分进行处理。大部分繁重的工作都发生在基础设施层。

您建议我如何处理这个问题?我觉得处理文档的两个存储库需要重新设计。

作为补充说明,我还考虑通过领域事件来协调其中一些工作。我认为我还没有准备好,而且这似乎比我现在需要添加的要复杂一些。尽管如此,我们也将不胜感激该领域的建议。