커스텀 액티비티

개요

커스텀 액티비티는 사용자가 정의한 Java 로직을 플로우에서 실행하며, 표준 액티비티로 처리하기 어려운 복잡한 비즈니스 로직을 직접 구현할 수 있도록 합니다.

커스텀 액티비티의 주요 특징은 다음과 같습니다.

  • Java 코드를 사용하여 복잡한 비즈니스 로직을 직접 구현할 수 있습니다.

  • AnyLinkCustomActivity 인터페이스를 구현하여 플로우에 사용자 정의 로직을 추가할 수 있습니다.

  • 플로우 컨텍스트에 접근하여 실행 환경의 데이터를 활용할 수 있습니다.

  • 메시지 조작 및 외부 시스템 연동을 통해 다양한 비즈니스 처리를 수행할 수 있습니다.

커스텀 액티비티 생성

리소스 트리에서 원하는 거래 또는 거래그룹의 컨텍스트 메뉴를 열고 [리소스 생성] > [커스텀 액티비티] > [Java 코드 타입 생성]을 선택합니다.

커스텀 액티비티 생성 화면은 [커스텀 액티비티 - Java 코드 타입] 탭과 [Java 에디터] 탭으로 구성됩니다. 설정 정보를 입력하고 탭 하단의 [생성] 버튼을 클릭합니다.

custom activity create

다음은 [커스텀 액티비티 - Java 코드 타입] 탭의 설정 항목입니다.

custom activity create basic info

커스텀 액티비티의 기본 정보를 입력합니다. (*: 필수 설정 항목)

항목 설명

커스텀 액티비티 아이디 *

커스텀 액티비티를 식별하기 위한 고유 ID입니다. 영문 대문자로 시작해야 하며, 영문, 숫자 및 언더스코어(_)를 조합하여 3~32자 이내로 입력합니다.

커스텀 액티비티 이름

커스텀 액티비티의 표시 이름입니다. 2~32자 이내로 입력해야 하며, 영문, 숫자 및 다음 특수문자를 사용할 수 있습니다.

  • 언더스코어(_), 하이픈(-), 대괄호([]), 중괄호({}), 괄호(()), 쉼표(,), 콜론(:)

미입력 시 아이디 값으로 자동 설정됩니다.

커스텀 액티비티 타임아웃 사용

커스텀 액티비티 실행 시 타임아웃 적용 여부를 설정합니다.

타임아웃 (ms)

커스텀 액티비티 타임아웃 사용을 활성화한 경우에만 표시됩니다.

커스텀 액티비티 실행의 최대 허용 시간을 밀리초(ms) 단위로 입력합니다. (기본값: 30,000)

설명

커스텀 액티비티에 대한 설명을 작성합니다.

다음은 Java 코드를 작성할 수 있는 [Java 에디터] 탭 화면입니다. [Java 에디터] 탭은 커스텀 액티비티 아이디가 유효해야만 전환됩니다. 아이디가 입력되지 않았거나 형식이 맞지 않으면 "커스텀 액티비티 아이디를 올바르게 입력하세요." 알림이 표시되며 탭이 전환되지 않습니다.

custom activity code editor

Java 에디터에서는 Java 코드 작성 시 다음과 같은 기능을 제공합니다.

  • 구문 하이라이팅

  • 자동 완성 기능

  • 오류 표시

  • 코드 초기화 기능

Java 코드 작성

Java 에디터에서 커스텀 액티비티의 Java 코드를 작성합니다.

커스텀 액티비티 아이디는 Java 클래스 이름으로 사용되므로 코드를 작성하기 전에 기본 정보 설정에서 커스텀 액티비티 아이디를 올바르게 설정해야 합니다.

커스텀 액티비티 아이디를 변경하면 코드 내 클래스 이름과 Logger 선언이 자동으로 변경됩니다.

public class ${이전 아이디} implements AnyLinkCustomActivity
→ public class ${새 아이디} implements AnyLinkCustomActivity

LogManager.getLogger(${이전 아이디}.class)
→ LogManager.getLogger(${새 아이디}.class)

코드를 작성하고 [생성] 버튼을 클릭하면 코드에 대한 검증이 수행됩니다. 코드에 에러가 있으면 "코드에 N개의 에러가 있습니다. 수정 후 다시 시도해주세요." 메시지가 표시되며 생성이 실패합니다.

[코드 초기화] 버튼을 클릭하면 코드가 기본 템플릿으로 초기화됩니다. 확인 대화상자가 표시되며, 확인 시 현재 코드가 기본 템플릿으로 대체됩니다.

[테마 변경] 버튼을 클릭하면 에디터의 테마를 다크 모드 또는 라이트 모드로 전환할 수 있습니다.

기본 템플릿

다음은 커스텀 액티비티 생성 시 제공되는 기본 템플릿입니다. 클래스 이름은 커스텀 액티비티 아이디와 동일하게 생성됩니다.

package com.tmaxsoft.anylink.custom;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.tmaxsoft.anylink.engine.core.activity.custom.AnyLinkCustomActivity;
import com.tmaxsoft.anylink.engine.core.activity.custom.CustomActivityContext;

public class MyCustomActivity implements AnyLinkCustomActivity {
    private static final Logger logger = LogManager.getLogger(MyCustomActivity.class);

    @Override
    public void execute(CustomActivityContext context) {
        // TODO Auto-generated method stub

    }
}
위 예시에서 MyCustomActivity는 커스텀 액티비티 아이디로서, 실제 생성 시 입력한 아이디로 대체됩니다.

커스텀 액티비티 코드에는 다음 요소가 반드시 포함되어야 합니다. 누락된 요소가 있으면 생성 시 오류가 발생합니다.

구분 필수 코드

log4j import

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

인터페이스 import

import com.tmaxsoft.anylink.engine.core.activity.custom.AnyLinkCustomActivity;
import com.tmaxsoft.anylink.engine.core.activity.custom.CustomActivityContext;

클래스 선언

public class ${아이디} implements AnyLinkCustomActivity {

Logger 선언

private static final Logger logger = LogManager.getLogger(${아이디}.class)

execute 메소드

public void execute(CustomActivityContext context) {

log4j Logger 선언은 필수입니다. 시스템 로그 처리를 위해 기본 템플릿에 포함된 import문과 Logger 선언을 유지해야 합니다.
다른 로깅 방식(SLF4J 등)을 사용할 경우 로그가 정상적으로 출력되지 않을 수 있습니다.

AnyLinkCustomActivity 인터페이스

커스텀 액티비티는 AnyLinkCustomActivity 인터페이스를 구현하여 작성되며, 다음은 해당 인터페이스의 정의와 메소드 구성에 대한 설명입니다.

public interface AnyLinkCustomActivity {

    /**
     * 초기화 - 액티비티 실행 전 한번 호출 (선택)
     * @param configuration BPMN에서 설정된 구성 정보
     */
    default void initialize(Map<String, Object> configuration) {
        // 기본 구현 - 필요시 오버라이드
    }

    /**
     * 실제 비즈니스 로직 실행 (필수)
     * @param context 액티비티 실행 컨텍스트
     */
    void execute(CustomActivityContext context);

    /**
     * 정리 작업 - 액티비티 실행 후 호출 (선택)
     * 예외 발생 시에도 호출됩니다.
     */
    default void cleanup() {
        // 기본 구현 - 필요시 오버라이드
    }

    /**
     * 실행 타임아웃 설정 (밀리초) (선택)
     * @return 타임아웃 시간 (기본 30초)
     */
    default long getTimeoutMillis() {
        return 30_000L;
    }
}
메소드 필수 여부 설명

initialize(Map<String, Object>)

선택

액티비티 실행 전 초기화 작업을 수행합니다. BPMN에서 설정된 구성 정보를 받습니다.

execute(CustomActivityContext)

필수

비즈니스 로직을 실행하는 핵심 메소드입니다.

cleanup()

선택

액티비티 실행 후 후처리 작업을 수행합니다. 예외 발생 시에도 호출됩니다.

getTimeoutMillis()

선택

액티비티의 실행 타임아웃 시간을 밀리초로 반환합니다. 기본값은 30,000 ms (30초)입니다.

CustomActivityContext 객체

커스텀 액티비티에서 플로우 컨텍스트에 접근하기 위한 객체입니다. 다음은 해당 객체의 주요 메소드입니다.

메소드 설명

getGuid()

현재 실행 중인 플로우의 고유 식별자(GUID)를 반환합니다.

getBizTxInfo()

거래 정보를 반환합니다. BizTxInfo.getBizTxId()로 거래 ID를 조회할 수 있습니다.

getEndpointInfo()

엔드포인트 정보를 반환합니다. EndpointInfo.getEndpointId()로 엔드포인트 ID를 조회할 수 있습니다.

getStartTime()

액티비티 실행 시작 시간을 반환합니다.

getVariables()

모든 플로우 변수를 변수명(key)과 값(value)으로 구성된 Map 형태로 반환합니다.

getVariable(String key)

지정된 키의 플로우 변수 값을 반환합니다.

getVariable(String key, Class<T> type)

지정된 키의 플로우 변수 값을 특정 타입으로 캐스팅하여 반환합니다.

setVariable(String key, Object value)

플로우 변수를 설정합니다.

removeVariable(String key)

지정된 키의 플로우 변수를 제거합니다.

getConfiguration()

BPMN에서 설정된 구성 정보를 설정 항목(key)과 값(value)으로 구성된 Map 형태로 반환합니다.

다음은 CustomActivityContext 객체의 주요 내부 클래스입니다. CustomActivityContext 객체는 내부 클래스를 통해 실행 환경의 다양한 정보를 제공합니다.

  • BizTxInfo

    거래 정보를 제공하는 클래스입니다. 현재 실행 중인 거래 정보를 조회할 때 사용됩니다.

    메소드 설명

    getBizTxId()

    현재 실행 중인 거래의 고유 식별자(ID)를 반환합니다.

    다음과 같이 메소드를 호출하여 값을 조회할 수 있습니다.

    context.getBizTxInfo().getBizTxId();
  • EndpointInfo

    엔드포인트 정보를 제공하는 클래스입니다. 현재 액티비티가 실행되는 엔드포인트 정보를 조회할 때 사용됩니다.

    메소드 설명

    getEndpointId()

    현재 실행 중인 엔드포인트의 고유 식별자(ID)를 반환합니다.

    다음과 같이 메소드를 호출하여 값을 조회할 수 있습니다.

    context.getEndpointInfo().getEndpointId();

Java 코드 작성 예시

변수 접근

커스텀 액티비티에서는 CustomActivityContext 객체를 통해 플로우 변수에 접근할 수 있습니다. 변수 조회, 설정, 삭제는 각각 제공되는 메소드를 사용하여 수행합니다.

@Override
public void execute(CustomActivityContext context) {
    // 변수 조회
    String orderId = (String) context.getVariable("orderId");
    Integer amount = context.getVariable("amount", Integer.class);

    // 변수 설정
    context.setVariable("processedAmount", amount * 1.1);
    context.setVariable("status", "PROCESSED");

    // 변수 제거
    context.removeVariable("tempData");
}

컨텍스트 정보 접근

CustomActivityContext 객체는 플로우 실행과 관련된 다양한 컨텍스트 정보를 제공합니다. 이를 통해 실행 식별자, 트랜잭션 정보, 엔드포인트 정보, 구성 정보 등을 조회할 수 있습니다.

@Override
public void execute(CustomActivityContext context) {
    // GUID 조회
    String guid = context.getGuid();

    // 거래 정보 조회
    CustomActivityContext.BizTxInfo bizTxInfo = context.getBizTxInfo();
    String bizTxId = bizTxInfo.getBizTxId();

    // 엔드포인트 정보 조회
    CustomActivityContext.EndpointInfo endpointInfo = context.getEndpointInfo();
    String endpointId = endpointInfo.getEndpointId();

    // 시작 시간 조회
    long startTime = context.getStartTime();

    // 구성 정보 조회
    Map<String, Object> config = context.getConfiguration();
}

외부 서비스 호출

커스텀 액티비티에서는 외부 시스템과 연동하여 데이터를 조회하거나 처리할 수 있습니다. HTTP 클라이언트 등 외부 라이브러리를 사용하여 서비스를 호출한 후, 결과를 변수에 저장할 수 있습니다.

외부 라이브러리를 사용하는 경우 의존성 추가가 필요합니다.

@Override
public void execute(CustomActivityContext context) {
    // HTTP 클라이언트 등을 사용한 외부 서비스 호출
    // 주의: 외부 라이브러리 사용 시 의존성 확인 필요

    String result = callExternalService();
    context.setVariable("externalResult", result);
}

private String callExternalService() {
    // 외부 서비스 호출 로직
    return "result";
}

조건부 로직

커스텀 액티비티에서는 플로우 변수 값을 기반으로 조건 분기 로직을 구현할 수 있습니다. 이를 통해 다양한 비즈니스 규칙을 코드로 처리할 수 있습니다.

@Override
public void execute(CustomActivityContext context) {
    String type = (String) context.getVariable("transactionType");
    Integer amount = context.getVariable("amount", Integer.class);

    String priority;
    if ("VIP".equals(type) || amount > 1000000) {
        priority = "HIGH";
    } else if (amount > 100000) {
        priority = "MEDIUM";
    } else {
        priority = "LOW";
    }

    context.setVariable("priority", priority);
}

로깅

커스텀 액티비티에서는 log4j Logger를 사용하여 실행 과정과 결과를 기록할 수 있습니다. CustomActivityContext를 통해 조회한 실행 정보(GUID, 트랜잭션 ID 등)를 로그에 포함하여 처리 흐름을 추적할 수 있습니다.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class MyCustomActivity implements AnyLinkCustomActivity {
    private static final Logger logger = LogManager.getLogger(MyCustomActivity.class);

    @Override
    public void execute(CustomActivityContext context) {
        String guid = context.getGuid();
        String bizTxId = context.getBizTxInfo().getBizTxId();

        logger.debug("Processing activity - guid={}, bizTxId={}", guid, bizTxId);

        // 처리 로직

        logger.info("Activity processed - guid={}, bizTxId={}", guid, bizTxId);
    }
}

초기화 및 정리

커스텀 액티비티는 initialize(), execute(), cleanup() 메소드를 통해 실행 전, 실행 중, 실행 후의 처리 흐름을 구성할 수 있습니다. 초기화 단계에서는 BPMN 설정 정보를 로드하고, 실행 단계에서는 해당 값을 사용하며, 후처리 단계에서는 리소스를 해제합니다.

import java.util.Map;

public class MyCustomActivity implements AnyLinkCustomActivity {
    private String configuredValue;

    @Override
    public void initialize(Map<String, Object> configuration) {
        // BPMN에서 설정된 구성 정보로 초기화
        this.configuredValue = (String) configuration.get("someKey");
    }

    @Override
    public void execute(CustomActivityContext context) {
        // 초기화된 값 사용
        context.setVariable("result", processWithConfig(configuredValue));
    }

    @Override
    public void cleanup() {
        // 리소스 정리
        this.configuredValue = null;
    }

    private String processWithConfig(String config) {
        return "processed: " + config;
    }
}

타임아웃 설정

커스텀 액티비티에서는 getTimeoutMillis() 메소드를 오버라이드하여 실행 타임아웃을 설정할 수 있습니다. 설정한 시간 내에 실행이 완료되지 않으면 해당 액티비티는 타임아웃 처리됩니다.

public class LongRunningActivity implements AnyLinkCustomActivity {

    @Override
    public void execute(CustomActivityContext context) {
        // 장시간 실행 로직
    }

    @Override
    public long getTimeoutMillis() {
        // 60초로 타임아웃 설정
        return 60_000L;
    }
}

예외 처리

예외 처리 방식은 비즈니스 요구사항에 따라 선택해야 합니다.

처리 방식 동작 사용 상황

예외를 다시 던짐 (throw)

액티비티 실패, 플로우 중단

필수 처리 실패

예외를 처리하고 종료

플로우 계속 진행

일부 실패 허용, 로깅 후 계속 처리

예외 발생 시 동작

커스텀 액티비티에서 예외가 발생하면 기본적으로 플로우 실행이 중단됩니다. 예외를 catch한 후 다시 throw하면 해당 액티비티 실행이 실패로 처리되며, 이후 플로우는 중단됩니다.

아래 예시는 예외 발생 시 오류 정보를 저장한 후 예외를 다시 던져 플로우 실행을 중단하는 방법입니다.

@Override
public void execute(CustomActivityContext context) {
    try {
        // 처리 로직
    } catch (Exception e) {
        // 오류 정보를 변수에 저장
        context.setVariable("errorMessage", e.getMessage());

        // 예외를 다시 던지면 플로우 중단
        throw new RuntimeException("Processing failed", e);
    }
}

오류 복구

예외 발생 시에도 플로우를 계속 진행하려면 예외를 catch하여 처리하고, 예외를 다시 던지지 않아야 합니다. 이 경우 액티비티는 정상적으로 완료된 것으로 간주되며, 이후 플로우가 계속 실행됩니다.

아래 예시는 예외를 처리하고 플로우를 계속 진행하는 방법을 보여줍니다.

@Override
public void execute(CustomActivityContext context) {
    try {
        // 처리 로직
        context.setVariable("success", true);
    } catch (Exception e) {
        logger.error("Processing failed - guid={}, reason={}",
            context.getGuid(), e.getMessage(), e);
        context.setVariable("success", false);
        context.setVariable("errorMessage", e.getMessage());
        // 예외를 던지지 않으면 플로우 계속 진행
    }
}

플로우에서 커스텀 액티비티 사용하기

다음은 플로우에서 리소스로 등록된 커스텀 액티비티를 사용하는 방법입니다.

  1. 플로우 편집기에서 팔레트의 액티비티 노드를 캔버스에 드래그합니다.

  2. 노드를 클릭하여 노드 편집 도구 모음에서 노드 타입을 '커스텀 액티비티'로 선택합니다.

  3. 프로퍼티 패널의 기본 정보 영역에서 플로우의 이름과 설명을 입력합니다.

  4. 서비스 호출 다이얼로그에서 사용할 커스텀 액티비티를 선택합니다.

  5. 파라미터 설정 영역에서 소스/타깃 변수 바인딩을 설정합니다.

비즈니스 로직 구현 예시

다음 예시는 주문 데이터를 검증하는 커스텀 액티비티 구현 예시입니다. 플로우 변수에서 주문 정보를 조회한 후, 고객 유형과 금액 조건에 따라 유효성을 판단하고 결과를 변수로 설정합니다.

주요 처리 과정은 변수 조회, 조건 검증, 결과 변수 설정 및 로그 기록으로 구성됩니다.

package com.tmaxsoft.anylink.custom;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.tmaxsoft.anylink.engine.core.activity.custom.AnyLinkCustomActivity;
import com.tmaxsoft.anylink.engine.core.activity.custom.CustomActivityContext;

public class OrderProcessingActivity implements AnyLinkCustomActivity {
    private static final Logger logger = LogManager.getLogger(OrderProcessingActivity.class);

    @Override
    public void execute(CustomActivityContext context) {
        String guid = context.getGuid();
        String bizTxId = context.getBizTxInfo().getBizTxId();

        String orderId = (String) context.getVariable("orderId");
        Integer amount = context.getVariable("amount", Integer.class);
        String customerType = (String) context.getVariable("customerType");

        logger.debug("Validating order - guid={}, orderId={}", guid, orderId);

        boolean isValid = true;
        String validationMessage = "OK";

        // 금액 검증
        if (amount == null || amount <= 0) {
            isValid = false;
            validationMessage = "Invalid amount";
        }

        // VIP 고객 한도 검증
        if ("VIP".equals(customerType) && amount > 10000000) {
            isValid = false;
            validationMessage = "Amount exceeds VIP limit";
        }

        // 일반 고객 한도 검증
        if (!"VIP".equals(customerType) && amount > 1000000) {
            isValid = false;
            validationMessage = "Amount exceeds normal limit";
        }

        context.setVariable("isValid", isValid);
        context.setVariable("validationMessage", validationMessage);

        logger.info("Order validation completed - guid={}, orderId={}, isValid={}", guid, orderId, isValid);
    }
}