admin管理员组

文章数量:1022424

I've been diving into Domain-Driven Design (DDD) lately and trying to apply it to a project at work. However, I've hit a bit of a snag and need your advice;

We have an approval system. Employees can review customer's application and decide whether to approve them. Due to differences in employee skill levels, junior employees can transfer approvals to senior employees when they cannot determine whether the application should be approved. So We design a workflow system. ApprovalFlow is AggregateRoot to handle all the approval actions by employees. Status is the state of aggregateRoot. ApproverId is the id of employee who review the workFlow. Its fields are as follows:

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

The approval rate and approval efficiency are the indicators that business departments pay attention to. In order to quantify these indicators, we require employees to provide relevant reasons when performing approval operations on the system (such as approving/rejecting customer applications, submitting them to senior employees for approval, etc.). The system will store the approval operations and reasons made by employees.

Now, when an employee decides to submit application to a senior employee or reject an application, they have to provide a relevant reason for the operation. Only approving operations, not the operation reasons, affect the state flow of the workflow. We tend to view the operation reasons as process data, such as operation logs, rather than business logic. Therefore, we did not model operation reasons as domain objects.

We are currently facing difficulties in the following two business scenarios:

  1. Junior employees submit customer applications to senior employees: We would like to record the operation reasons for submitting to senior employees in the approval operation log;
  2. Employee rejects a customer application: We would like to record the reason for rejection in the approval operation log;
// 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);
}

Previous attempts: We previously came up with two solutions:

  1. When handling domain events, write the corresponding approval operation log. Then find this approval operation log in the query model and fill in the reason for the operation. But we found it difficult to find this approval operation log;
  2. Temporarily store the reason for the operation, query the reason for the operation when handling domain events. Then construct an approval operation log and save it. But we found that this method essentially bypasses the domain model.

Could anyone help me figure out a solution? Thanks!


I've been diving into Domain-Driven Design (DDD) lately and trying to apply it to a project at work. However, I've hit a bit of a snag and need your advice;

We have an approval system. Employees can review customer's application and decide whether to approve them. Due to differences in employee skill levels, junior employees can transfer approvals to senior employees when they cannot determine whether the application should be approved. So We design a workflow system. ApprovalFlow is AggregateRoot to handle all the approval actions by employees. Status is the state of aggregateRoot. ApproverId is the id of employee who review the workFlow. Its fields are as follows:

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

The approval rate and approval efficiency are the indicators that business departments pay attention to. In order to quantify these indicators, we require employees to provide relevant reasons when performing approval operations on the system (such as approving/rejecting customer applications, submitting them to senior employees for approval, etc.). The system will store the approval operations and reasons made by employees.

Now, when an employee decides to submit application to a senior employee or reject an application, they have to provide a relevant reason for the operation. Only approving operations, not the operation reasons, affect the state flow of the workflow. We tend to view the operation reasons as process data, such as operation logs, rather than business logic. Therefore, we did not model operation reasons as domain objects.

We are currently facing difficulties in the following two business scenarios:

  1. Junior employees submit customer applications to senior employees: We would like to record the operation reasons for submitting to senior employees in the approval operation log;
  2. Employee rejects a customer application: We would like to record the reason for rejection in the approval operation log;
// 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);
}

Previous attempts: We previously came up with two solutions:

  1. When handling domain events, write the corresponding approval operation log. Then find this approval operation log in the query model and fill in the reason for the operation. But we found it difficult to find this approval operation log;
  2. Temporarily store the reason for the operation, query the reason for the operation when handling domain events. Then construct an approval operation log and save it. But we found that this method essentially bypasses the domain model.

Could anyone help me figure out a solution? Thanks!


Share Improve this question edited Nov 21, 2024 at 4:03 Jeremy Huang asked Nov 19, 2024 at 8:45 Jeremy HuangJeremy Huang 11 bronze badge 4
  • "when an employee decides to reject an application, they have to provide a reject reason" Why is that a requirement? What would happen if they didn't? – Mark Seemann Commented Nov 20, 2024 at 9:00
  • @MarkSeemann Thanks for your question! The approval rate is our business indicator. The business department analyzes the reasons for approval rejection and adjusts the approval strategy. Therefore, we have implemented restrictions on the system. If an employee rejects an application, they need to provide a reason for rejection, otherwise they cannot reject the application – Jeremy Huang Commented Nov 20, 2024 at 16:23
  • Isn't it a business rule, then? If it is, then why not making the rejection reason part of the domain model? – Mark Seemann Commented Nov 20, 2024 at 17:33
  • @MarkSeemann Because the rejection reason is recorded to support business department review and optimize their approval strategy, it does not affect employees' approval of customer applications. I have added more details in the problem description, please take a look again. – Jeremy Huang Commented Nov 21, 2024 at 6:06
Add a comment  | 

1 Answer 1

Reset to default 0

It sounds as though you consider the reason for an action as belonging to a subdomain different from the workflow subdomain. You may have good reasons to do so, but it does make things more complicated. It's always worth considering whether a design choice is worth the extra complexity. Let us, however, for the sake of argument assume that this is the case.

A typical solution to this kind of problem is to use a correlation ID. In asynchronous, message-based systems, this is usually a good idea anyway. Make sure that each entity has a unique ID, preferably by assign a UUID to it when it first comes into existence. Use this ID with all messages related to that entity. Thus, when a workflow is rejected, the system may post multiple messages, in this case:

  • A rejection command (reject)
  • A reason event (workflowRejected)

Despite being different, each message has the same correlation ID. This enables you to implement an Aggregator if you later need to gather together both the application and the reason given for its current status.

I've been diving into Domain-Driven Design (DDD) lately and trying to apply it to a project at work. However, I've hit a bit of a snag and need your advice;

We have an approval system. Employees can review customer's application and decide whether to approve them. Due to differences in employee skill levels, junior employees can transfer approvals to senior employees when they cannot determine whether the application should be approved. So We design a workflow system. ApprovalFlow is AggregateRoot to handle all the approval actions by employees. Status is the state of aggregateRoot. ApproverId is the id of employee who review the workFlow. Its fields are as follows:

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

The approval rate and approval efficiency are the indicators that business departments pay attention to. In order to quantify these indicators, we require employees to provide relevant reasons when performing approval operations on the system (such as approving/rejecting customer applications, submitting them to senior employees for approval, etc.). The system will store the approval operations and reasons made by employees.

Now, when an employee decides to submit application to a senior employee or reject an application, they have to provide a relevant reason for the operation. Only approving operations, not the operation reasons, affect the state flow of the workflow. We tend to view the operation reasons as process data, such as operation logs, rather than business logic. Therefore, we did not model operation reasons as domain objects.

We are currently facing difficulties in the following two business scenarios:

  1. Junior employees submit customer applications to senior employees: We would like to record the operation reasons for submitting to senior employees in the approval operation log;
  2. Employee rejects a customer application: We would like to record the reason for rejection in the approval operation log;
// 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);
}

Previous attempts: We previously came up with two solutions:

  1. When handling domain events, write the corresponding approval operation log. Then find this approval operation log in the query model and fill in the reason for the operation. But we found it difficult to find this approval operation log;
  2. Temporarily store the reason for the operation, query the reason for the operation when handling domain events. Then construct an approval operation log and save it. But we found that this method essentially bypasses the domain model.

Could anyone help me figure out a solution? Thanks!


I've been diving into Domain-Driven Design (DDD) lately and trying to apply it to a project at work. However, I've hit a bit of a snag and need your advice;

We have an approval system. Employees can review customer's application and decide whether to approve them. Due to differences in employee skill levels, junior employees can transfer approvals to senior employees when they cannot determine whether the application should be approved. So We design a workflow system. ApprovalFlow is AggregateRoot to handle all the approval actions by employees. Status is the state of aggregateRoot. ApproverId is the id of employee who review the workFlow. Its fields are as follows:

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

The approval rate and approval efficiency are the indicators that business departments pay attention to. In order to quantify these indicators, we require employees to provide relevant reasons when performing approval operations on the system (such as approving/rejecting customer applications, submitting them to senior employees for approval, etc.). The system will store the approval operations and reasons made by employees.

Now, when an employee decides to submit application to a senior employee or reject an application, they have to provide a relevant reason for the operation. Only approving operations, not the operation reasons, affect the state flow of the workflow. We tend to view the operation reasons as process data, such as operation logs, rather than business logic. Therefore, we did not model operation reasons as domain objects.

We are currently facing difficulties in the following two business scenarios:

  1. Junior employees submit customer applications to senior employees: We would like to record the operation reasons for submitting to senior employees in the approval operation log;
  2. Employee rejects a customer application: We would like to record the reason for rejection in the approval operation log;
// 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);
}

Previous attempts: We previously came up with two solutions:

  1. When handling domain events, write the corresponding approval operation log. Then find this approval operation log in the query model and fill in the reason for the operation. But we found it difficult to find this approval operation log;
  2. Temporarily store the reason for the operation, query the reason for the operation when handling domain events. Then construct an approval operation log and save it. But we found that this method essentially bypasses the domain model.

Could anyone help me figure out a solution? Thanks!


Share Improve this question edited Nov 21, 2024 at 4:03 Jeremy Huang asked Nov 19, 2024 at 8:45 Jeremy HuangJeremy Huang 11 bronze badge 4
  • "when an employee decides to reject an application, they have to provide a reject reason" Why is that a requirement? What would happen if they didn't? – Mark Seemann Commented Nov 20, 2024 at 9:00
  • @MarkSeemann Thanks for your question! The approval rate is our business indicator. The business department analyzes the reasons for approval rejection and adjusts the approval strategy. Therefore, we have implemented restrictions on the system. If an employee rejects an application, they need to provide a reason for rejection, otherwise they cannot reject the application – Jeremy Huang Commented Nov 20, 2024 at 16:23
  • Isn't it a business rule, then? If it is, then why not making the rejection reason part of the domain model? – Mark Seemann Commented Nov 20, 2024 at 17:33
  • @MarkSeemann Because the rejection reason is recorded to support business department review and optimize their approval strategy, it does not affect employees' approval of customer applications. I have added more details in the problem description, please take a look again. – Jeremy Huang Commented Nov 21, 2024 at 6:06
Add a comment  | 

1 Answer 1

Reset to default 0

It sounds as though you consider the reason for an action as belonging to a subdomain different from the workflow subdomain. You may have good reasons to do so, but it does make things more complicated. It's always worth considering whether a design choice is worth the extra complexity. Let us, however, for the sake of argument assume that this is the case.

A typical solution to this kind of problem is to use a correlation ID. In asynchronous, message-based systems, this is usually a good idea anyway. Make sure that each entity has a unique ID, preferably by assign a UUID to it when it first comes into existence. Use this ID with all messages related to that entity. Thus, when a workflow is rejected, the system may post multiple messages, in this case:

  • A rejection command (reject)
  • A reason event (workflowRejected)

Despite being different, each message has the same correlation ID. This enables you to implement an Aggregator if you later need to gather together both the application and the reason given for its current status.

本文标签: javahow to transfer nondomainobject data to event handlerStack Overflow