如何将非域对象数据传输到事件处理程序

问题描述 投票:0回答:1

我最近一直在深入研究领域驱动设计(DDD),并尝试将其应用到工作项目中。然而,我遇到了一些障碍,需要你的建议;

我们有审批系统。员工可以审核客户的申请并决定是否批准。由于员工技能水平存在差异,初级员工在无法确定是否批准申请时,可以将审批转给高级员工。 因此我们设计了一个工作流程系统。

ApprovalFlow
是AggregateRoot,用于处理员工的所有审批操作。
Status
是aggregateRoot的状态。
ApproverId
是审核工作流程的员工ID。其字段如下:

ApprovalFlow {
    private EventBus eventBus;

    private Long approvalFlowId;
    private Status status;
    private ApprovalResult approvalResult;
    private ApproverId approverId;

    public void associate(ApproverId approverId) {
        this.approverId = approverId;
        eventBus.push(new AssociateEvent(approvalFlowId, approverId));
    }

    public void approve() {
        this.status = Status.COMPLETED;
        this.approvalResult = ApprovalResult.PASS;
        eventBus.push(new ApproveEvent(approvalFlowId))
    }

    public void reject() {
        this.status = Status.COMPLETED;
        this.approvalResult = ApprovalResult.REJECT;
        eventBus.push(new RejectEvent(approvalFlowId));
    }

    enum Status {
        APPROVING(1),
        COMPLETED(2);
        private Integer code;
        Status(Integer code) {
            this.code = code;
        }
    }

    enum ApprovalResult {
        PASS(1),
        REJECT(2);
        private Integer code;
        Status(Integer code) {
            this.code = code;
        }
    }
}

审批率和审批效率是业务部门关注的指标。为了量化这些指标,我们要求员工在系统上进行审批操作时(如批准/拒绝客户申请、提交高级员工审批等)提供相关理由。系统会存储员工的审批操作及理由。

现在,当员工决定向高级员工提交申请或拒绝申请时,必须提供相关操作理由。只有批准操作,而不是操作原因影响工作流的状态流程。我们倾向于查看 操作原因作为流程数据,例如操作日志,而不是业务逻辑。因此,我们没有将操作原因建模为领域对象。

我们目前面临以下两个业务场景的困难

  1. 初级员工向高级员工提交客户申请:我们希望在审批操作日志中记录向高级员工提交客户申请的操作原因;
  2. 员工拒绝客户申请:我们希望记录该情况 审批操作日志中拒绝原因;
// submit customer's application to senior employee scenario
// remote api method
public void submitNextApprover(Long approverFlowId, Long nextApproverId, String reason) {
    check(approvalFlowId != null, "approvalFlowId cannot be null"); 
    check(nextApproverId != null, "nextApproverId cannot be null"); 
    check(StringUtils.isNotBlank(reason), "reason cannot be blank");

    commandService.submitNextApprover(approverFlowId, nextApproverId);
}

// command service
public void submitNextApprover(Long approvalFlowId, Long nextApproverId) {
    ApprovalFlow approvalFlow = approvalRepository.find(approvalFlowId);
    approvalFlow.associate(new ApproverId(nextApproverId));
    approvalRepository.save(approvalFlow);
}

// AggregateRoot associate method
public void associate(ApproverId approverId) {
    this.approverId = approverId;
    eventBus.push(new AssociateEvent(approvalFlowId, approverId));
}

// domain event handler and write a submitNextApprover log;
@Subscribe
public void handleEvent(AssociateEvent associateEvent) {
    
    AssociateLogPo associateLog = new AssociateLogPo();
    associateLog.setApprovalFlowId(associateEvent.getApprovalFlowId);
    associateLog.setApproverId(associateEvent.getApproverId);
//    can't transfer reason through associateEvent to event handler,
//    because the reason is not a domain object
    associateLog.setReason(reason);

    associateLogMapper.insert(associateLog);
}




// reject customer's application scenario
// remote api method
public void rejectApproval(Long approvalFlowId, String rejectReason) {
    check(approvalFlowId != null, "approvalFlowId cannot be null"); 
    check(StringUtils.isNotBlank(rejectReason), "rejectReason cannot be blank");

    commandService.rejectApproval(Long approvalFlowId);
}

// command service
public void rejectApproval(Long approvalFlowId) {
    ApprovalFlow approvalFlow = approvalRepository.find(approvalFlowId);
    approvalFlow.reject();
    approvalRepository.save(approvalFlow);
}

// AggregateRoot reject method
public void reject() {
    this.status = Status.COMPLETED;
    this.approvalResult = ApprovalResult.REJECT;
    eventBus.push(new RejectEvent(approvalFlowId));
}

// domain event handler and write a reject approval log;
@Subscribe
public void handleEvent(RejectEvent rejectEvent) {
    
    RejectLogPo rejectLog = new RejectLogPo();
    rejectLog.setApprovalFlowId(rejectEvent.getApprovalFlowId);
//    can't transfer rejectReason through rejectEvent to event handler,
//    because the rejectReason is not a domain object
    rejectLog.setRejectReason(rejectReason);

    rejectLogMapper.insert(rejectLog);
}

之前的尝试: 我们之前提出了两个解决方案:

  1. 处理领域事件时,写入相应的审批操作日志。然后在查询模型中找到这条审批操作日志,填写操作原因。但我们发现这个审批操作日志很难找到;
  2. 临时存储操作原因,在处理领域事件时查询操作原因。然后构建审批操作日志并保存。但我们发现这种方法本质上绕过了领域模型。

有人可以帮我想出解决办法吗?谢谢!


java oop design-patterns domain-driven-design
1个回答
0
投票

听起来好像您认为某个操作的原因属于与workflow子域不同的子域。您可能有充分的理由这样做,但这确实使事情变得更加复杂。设计选择是否值得额外的复杂性总是值得考虑的。然而,为了便于论证,让我们假设情况确实如此。

此类问题的典型解决方案是使用关联 ID。在异步、基于消息的系统中,这通常是一个好主意。确保每个实体都有唯一的 ID,最好在它第一次存在时为其分配一个 UUID。将此 ID 与与该实体相关的所有消息一起使用。因此,当工作流被拒绝时,系统可能会发布多条消息,在这种情况下:

  • 拒绝命令(
    reject
    )
  • 一个原因事件(
    workflowRejected
    )

尽管不同,但每条消息都具有相同的关联 ID。如果您稍后需要将应用程序及其当前状态给出的原因收集在一起,这使您能够实现聚合器

© www.soinside.com 2019 - 2024. All rights reserved.