Miklix

SysExtension 프레임워크를 사용하여 Dynamics AX 2012에서 인스턴스화할 하위 클래스 찾기

게시됨: 2025년 2월 16일 오전 12시 25분 49초 UTC
마지막으로 업데이트되었습니다: 2026년 1월 12일 오전 8시 43분 4초 UTC

이 문서에서는 Dynamics AX 2012 및 Dynamics 365 for Operations에서 잘 알려지지 않은 SysExtension 프레임워크를 사용하여 속성 데코레이션을 기반으로 하위 클래스를 인스턴스화하는 방법을 설명합니다. 이를 통해 처리 클래스 계층 구조를 쉽게 확장할 수 있도록 설계할 수 있습니다.


이 페이지는 가능한 한 많은 사람이 이용할 수 있도록 영어에서 기계 번역되었습니다. 안타깝게도 기계 번역은 아직 완성된 기술이 아니므로 오류가 발생할 수 있습니다. 원하시는 경우 여기에서 영어 원문을 보실 수 있습니다:

Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012

이 게시물의 정보는 Dynamics AX 2012 R3을 기준으로 작성되었습니다. 다른 버전에서는 유효하지 않을 수 있습니다. (업데이트: 이 글의 정보는 Dynamics 365 for Operations에서도 유효한 것으로 확인되었습니다.)

Dynamics AX에서 처리 클래스를 구현할 때, 각 하위 클래스가 열거형 값에 대응하거나 다른 데이터 연결을 갖는 클래스 계층 구조를 만들어야 하는 경우가 종종 있습니다. 이때 일반적인 설계 방식은 상위 클래스에 생성자 메서드를 만들고, 이 메서드에 switch 문을 사용하여 입력값에 따라 어떤 클래스를 인스턴스화할지 결정하는 것입니다.

원칙적으로는 잘 작동하지만, 입력값이 여러 가지인 경우(열거형에 요소가 많거나 입력값이 여러 값의 조합인 경우) 유지 관리가 번거롭고 오류가 발생하기 쉬울 수 있습니다. 또한, 새로운 서브클래스를 추가하거나 입력값에 따라 어떤 서브클래스를 사용할지 변경해야 할 때마다 해당 생성자 메서드를 수정해야 한다는 단점이 있습니다.

다행히도, 이 작업을 수행하는 훨씬 더 우아하지만, 불행히도 덜 알려진 방법이 있는데, 바로 SysExtension 프레임워크를 사용하는 것입니다.

이 프레임워크는 서브클래스를 꾸밀 때 사용할 수 있는 속성을 활용하여 시스템이 어떤 서브클래스가 어떤 기능을 처리해야 하는지 쉽게 파악할 수 있도록 합니다. 생성자 메서드는 여전히 필요하지만, 제대로 구현한다면 새로운 서브클래스를 추가할 때마다 수정할 필요가 없습니다.

가상의 예를 들어, InventTrans 테이블을 기반으로 어떤 처리를 수행하는 계층 구조를 구현한다고 가정해 보겠습니다. 어떤 처리를 수행할지는 레코드의 StatusReceipt와 StatusIssue, 그리고 레코드가 SalesLine, PurchLine과 관련이 있는지 여부에 따라 달라집니다. 벌써부터 다양한 조합이 존재하죠.

지금 당장은 몇 가지 조합만 처리하면 되지만, 시간이 지남에 따라 점점 더 많은 조합을 처리할 수 있어야 한다는 것을 알고 있다고 가정해 봅시다.

간단하게 설명하자면, 현재로서는 StatusIssue가 ReservPhysical 또는 ReservOrdered인 SalesLine 관련 레코드만 처리하면 된다고 가정해 보겠습니다. 다른 모든 조합은 지금은 무시해도 되지만, 나중에 처리해야 할 수도 있으므로 코드를 쉽게 확장할 수 있도록 설계하는 것이 좋습니다.

현재로서는 계층 구조가 대략 다음과 같을 수 있습니다.

  • MyProcessorMyProcessor_SalesMyProcessor_Sales_ReservOrderedMyProcessor_Sales_ReservPhysical

물론, 상위 클래스에 ModuleInventPurchSales와 StatusIssue 열거형을 기반으로 하위 클래스를 인스턴스화하는 메서드를 쉽게 구현할 수 있습니다. 하지만 이렇게 하면 하위 클래스를 추가할 때마다 상위 클래스를 수정해야 하는데, 이는 객체 지향 프로그래밍에서 상속의 본래 취지에 맞지 않습니다. 결국, 새로운 배치 작업을 추가할 때마다 RunBaseBatch나 SysOperationServiceBase를 수정할 필요는 없으니까요.

대신 SysExtension 프레임워크를 활용할 수 있습니다. 이를 위해서는 SysAttribute를 상속하는 새로운 클래스를 추가해야 합니다. 이 클래스는 처리 클래스에 적용할 수 있는 속성으로 사용됩니다.

이 클래스는 SysOperation 구현을 위한 데이터 계약 클래스를 만드는 방식과 매우 유사하며, 데이터 멤버와 해당 값을 가져오고 설정하는 매개변수 메서드를 포함합니다.

우리 경우에는 클래스 선언이 다음과 같을 수 있습니다.

class MyProcessorSystemAttribute extends SysAttribute
{
    ModuleInventPurchSales  module;
    StatusIssue             statusIssue;
    StatusReceipt           statusReceipt
}

모든 데이터 멤버를 인스턴스화하는 `new()` 메서드를 만들어야 합니다. 원한다면 일부 또는 모든 멤버에 기본값을 지정할 수 있지만, 저는 그렇게 하지 않았습니다.

public void new(ModuleInventPurchSales  _module,
                StatusIssue             _statusIssue,
                StatusReceipt           _statusReceipt)
{
    ;

    super();

    module          = _module;
    statusIssue     = _statusIssue;
    statusReceipt   = _statusReceipt;
}

또한 각 데이터 멤버에 대한 매개변수 메서드를 구현해야 하지만, 여러분이 이미 방법을 알고 있을 것이라고 생각하여 여기서는 생략했습니다. 혹시 모르신다면, 연습 문제로 생각해도 좋겠습니다 ;-)

이제 속성 클래스를 사용하여 각 처리 클래스를 꾸밀 수 있습니다. 예를 들어 클래스 선언은 다음과 같을 수 있습니다.

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::None,
                            StatusReceipt::None)]
class MyProcessor_Sales extends MyProcessor
{
}

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::ReservOrdered,
                            StatusReceipt::None)]
class MyProcessor_Sales_ReservOrdered extends MyProcessor_Sales
{
}

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::ReservPhysical,
                            StatusReceipt::None)]
class MyProcessor_Sales_ReservPhysical extends MyProcessor_Sales
{
}

클래스 이름은 원하는 대로 지정할 수 있지만, 중요한 것은 클래스가 수행하는 처리 유형에 해당하는 속성을 클래스에 추가하는 것입니다. (단, Dynamics AX에는 클래스 계층 구조에 대한 명명 규칙이 있으므로 가능하면 이를 따르는 것이 좋습니다.)

이제 클래스에 각 클래스가 수행하는 처리 유형을 식별하도록 데코레이터를 추가했으므로 SysExtension 프레임워크를 활용하여 필요에 따라 하위 클래스의 객체를 인스턴스화할 수 있습니다.

상위 클래스(MyProcessor)에 다음과 같은 생성자 메서드를 추가할 수 있습니다.

public static MyProcessor construct(ModuleInventPurchSales _module,
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
    MyProcessor                 ret;
    MyProcessorSystemAttribute  attribute;
    ;

    attribute = new MyProcessorSystemAttribute( _module,
                                                _statusIssue,
                                                _statusReceipt);

    ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MyProcessor), attribute);

    if (!ret)
    {
        //  no class found
        //  here you could throw an error, instantiate a default
        //  processor instead, or just do nothing, up to you
    }

    return ret;
}

정말 흥미로운 부분이자 이 글 전체의 핵심(말장난 죄송합니다)은 SysExtensionAppClassFactory 클래스의 getClassFromSysAttribute() 메서드입니다. 이 메서드는 계층 구조의 상위 클래스 이름(이 상위 클래스는 계층 구조의 최상위에 있을 필요는 없으며, 단지 이 클래스를 확장하는 클래스만 사용 가능하다는 의미입니다)과 속성 객체를 인수로 받습니다.

그러면 지정된 상위 클래스를 상속하는 클래스의 객체를 반환하고, 해당 속성으로 장식합니다.

생성자 메서드에 원하는 만큼 추가적인 유효성 검사나 로직을 추가할 수 있지만, 여기서 중요한 점은 한 번 구현하고 나면 이 메서드를 다시 수정할 필요가 없다는 것입니다. 하위 클래스를 계층 구조에 추가하고 적절하게 데코레이터를 적용하기만 하면, 생성자 메서드는 작성 당시에는 존재하지 않았던 하위 클래스도 찾아낼 수 있습니다.

성능은 어떨까요? 솔직히 벤치마킹을 시도해 보지는 않았지만, 제 생각에는 기존의 switch 문 방식보다 성능이 떨어질 것 같습니다. 하지만 Dynamics AX에서 성능 문제가 발생하는 주된 원인이 데이터베이스 접근이라는 점을 고려하면, 크게 걱정할 필요는 없을 것 같습니다.

물론, 수천 개의 객체를 빠르게 생성해야 하는 기능을 구현하는 경우에는 추가적인 조사가 필요할 수 있지만, 단순히 하나의 객체를 생성하여 시간이 오래 걸리는 처리를 하는 일반적인 경우에는 큰 문제가 되지 않을 것으로 생각합니다. 또한, 다음 단락에서 설명하는 문제 해결 팁을 고려해 볼 때, SysExtension 프레임워크는 캐싱에 의존하는 것으로 보이므로, 실행 중인 시스템에서는 성능 저하가 크지 않을 것으로 예상됩니다.

문제 해결: 생성자 메서드가 서브클래스를 찾지 못하는 경우(데코레이터가 올바르게 적용되었는지 확인했음에도 불구하고) 캐싱 문제일 수 있습니다. 클라이언트와 서버 모두에서 캐시를 지워보세요. AOS를 재시작할 필요는 없지만, 최후의 수단으로 고려해 볼 수 있습니다.

추가 자료

이 글이 마음에 드셨다면 다음 제안도 마음에 드실 겁니다.


블루스카이에서 공유하기페이스북에서 공유하기LinkedIn에서 공유하기Tumblr에 공유하기X에서 공유LinkedIn에서 공유하기Pinterest에 고정

미켈 크리스텐슨

저자 소개

미켈 크리스텐슨
남자 이름은 miklix.com의 창시자이자 소유자입니다. 전문 컴퓨터 프로그래머/소프트웨어 개발자로 20년 이상 경력을 쌓았으며 현재 유럽의 대형 IT 기업에서 정규직으로 근무하고 있습니다. 블로그를 운영하지 않을 때는 여가 시간을 다양한 관심사, 취미, 활동으로 보내며 이 웹사이트에서 다루는 다양한 주제에 어느 정도 반영되어 있습니다.