IMS 워크플로우 만들기

1. 요구사항 및 기능 분석

1-1. 워크플로우 기능 및 구조

  • 워크플로우는 상태(Status)와 전환(Transition)을 관리하는 서비스
  • 워크플로우를 통해 이슈 처리 프로세스를 사용자가 직접 지정할 수 있음
  • 워크플로우 구조 및 기능
    • 시작 상태 (default)
    • 상태 생성
      • 시작 상태, 진행상태, 종료 상태 나뉨
      • 시작 상태는 시작과 연결되면 시작 상태이며, 단 1개만 존재 가능
      • 종료 상태에서는 전환이 생길 수 없음
      • 상태명 지정 가능
    • 전환 생성
      • 시작 상태와 종료 상태를 연결함
      • 전환명 지정 가능
      • 전환마다 규칙을 생성할 수 있음
      • 전환이 동일할경우, 동일한 규칙과 전환명을 가짐 → ex. REJECTED에서 CLOSED로 가는 전환과 RESOLVED에서 CLOSED로 가는 전환은 동일한 close라는 이름을 가진 전환이며, 해당 전환에 종료일 자동 생성이라는 기능을 추가하면 동일하게 적용 받음

1-2. 유사 시스템 (Jira) 예시

image.png

image.png

image.png

image.png

2. 구현 아이디어 및 적용

2-1. ERD 설계

image.png

2-2. 워크플로우 상태 및 전환 관리 서비스 로직

  • WorkflowService 주요 기능
    • 상태와 전환의 생성, 수정, 삭제 작업을 처리하는 서비스 로직
      • 워크플로우 상태 관리: 상태 추가, 수정, 삭제
      • 워크플로우 전환 관리: 전환 추가, 수정, 삭제
      • 생성, 수정, 삭제 API를 따로 분리하여 호출하지 않고, 최종적인 상태만 FE로 부터 받아서 처리
    • 검증 로직
      • 단일 시작 전환 체크 : 시작 전환이 단 하나만 존재하도록 검증
      • 상태/전환의 고유성 검증 : 각 상태와 전환 ID가 고유한지 체크

2-3. 상태/전환의 수정, 생성, 삭제 한번에 처리 방안 (API 분리 X)

  • UpdateWorkflowService 클래스
    • 워크플로우 전체 상태와 전환을 관리하는 서비스의 핵심
    • 각각의 상태와 전환을 처리하는 별도의 서브 서비스를 호출해 관리
 public class WorkflowService extends Service<WorkflowRequest, Void> {
    CreateWorkflowStatusService createWorkflowStatusService = new CreateWorkflowStatusService();
    UpdateWorkflowStatusService updateWorkflowStatusService = new UpdateWorkflowStatusService();
    DeleteWorkflowStatusService deleteWorkflowStatusService = new DeleteWorkflowStatusService();
    CreateWorkflowTransitionService createWorkflowTransitionService = new CreateWorkflowTransitionService();
    UpdateWorkflowTransitionService updateWorkflowTransitionService = new UpdateWorkflowTransitionService();
    DeleteWorkflowTransitionService deleteWorkflowTransitionService = new DeleteWorkflowTransitionService();

    @Override
    public Void doService(UpdateWorkflowRequest request, SqlSession sqlSession) {
        StatusMapper statusMapper = sqlSession.getMapper(StatusMapper.class);
        TransitionMapper transitionMapper = sqlSession.getMapper(TransitionMapper.class);

        // 상태와 전환을 처리하고, 삭제할 상태 및 전환을 반환
        Set<Long> toBeDeletedStatusIds = processStatuses(sqlSession, statusMapper, request.getWorkflowStatusRequestList());
        Set<Long> toBeDeletedTransitionIds = processTransitions(sqlSession, transitionMapper, request.getWorkflowTransitionRequestList());

        // 삭제 작업 수행
        deleteStatuses(toBeDeletedStatusIds, sqlSession);
        deleteTransitions(toBeDeletedTransitionIds, sqlSession);
        return null;
    }
}
  • processStatuses 메서드
    • 새로운 상태를 생성하고, 기존 상태를 업데이트함.
    • existingStatusIds : DB에 존재하는 StatusId Set을 가져옴
    • createStatuses
      • List에서 isNewStatus()를 통해 id가 null인지 체크함.
      • 즉, id가 존재하지 않는다면 새로 생성된 Status로 간주해서 DB에 추가함
    • updateStatuses
      • List에서 isExistingStatus()통해 DB에 존재하는 StatusId인지 체크
      • 즉, Request로 들어온 값 중에 기존에 존재했다면 수정해야할 Status로 간주
      • 수정한 Status는 existingStatusIds 에서 삭제함 → 최종적으로 삭제할 Status만 남음
      • 모든 상태가 처리된 후에는 더 이상 사용되지 않는 상태를 찾아서 삭제할 수 있도록 existingStatusIds에 남아 있는 상태 ID를 반환함
    • deleteStatuses : 최종적으로 남은 StatusId에 해당하는 상태는 삭제함

image.png

private Set<Long> processStatuses(SqlSession sqlSession, StatusMapper statusMapper, List<StatusRequest> statusRequestList) {
    Set<Long> existingStatusIds = findAllStatusIds(statusMapper);

    // 새 상태 생성
    createStatuses(statusRequestList, sqlSession);
    
    // 기존 상태 업데이트
    updateStatuses(existingStatusIds, statusRequestList, sqlSession);
    
    return existingStatusIds; // 더 이상 사용되지 않는 상태 반환
}

private void createStatuses(List<StatusRequest> statusRequestList, SqlSession sqlSession) {
    statusRequestList.stream()
        .filter(this::isNewStatus)
        .forEach(statusRequest -> createStatus(statusRequest, sqlSession));
}

private void updateStatuses(Set<Long> statusIds, List<StatusRequest> statusRequestList, SqlSession sqlSession) {
    statusRequestList.stream()
        .filter(statusRequest -> isExistingStatus(statusIds, statusRequest))
        .forEach(statusRequest -> {
            updateStatus(statusRequest, sqlSession);
            statusIds.remove(statusRequest.getStatusId());
        });
}