Skip to content

Fox Biz Service 고급 사용법

Fox Biz Service 는 백엔드의 비즈니스 로직을 손쉽게 호출할 수 있도록 설계되었습니다. 특히, 대규모의 백엔드 시스템을 개발하고 유지보수를 할 때 발생할 수 있는 다양한 문제들을 최소화하여 개발 생산성과 유지보수 편의성을 크게 높이는 부분에 중점을 두고 있습니다. 이 문서에서는 Fox Biz Service 의 이러한 고급 기능들과 사용법에 대해 자세히 설명합니다.

비즈 모듈 관리

기업 내의 업무 시스템과 같은 대규모 환경에서는 수백, 수천 개의 비즈니스 로직이 존재할 수 있습니다. 이들 비즈니스 로직을 하나의 모듈(어셈블리)에 포함시킨다면 간단한 메서드 수정에도 전체 코드를 빌드 배포해야 하는 등 다양한 유지/보수 문제를 유발할 수 있습니다. 따라서 비즈니스 로직들은 적절한 규모와 분류에 따라서 여러 모듈로 분리되며 이렇게 함으로써 공통 메서드들을 관리하기도 쉬워집니다.

Fox Biz Service 에서는 비즈 클래스를 포함하는 어셈블리를 비즈 모듈이라 부르며 FoxBizServiceConfig.Configure 정적 메서드가 호출되는 시점에서 등록된 비즈 모듈들을 로드하는 초기화 과정을 수행 합니다. 로드할 비즈 모듈들은 구성 설정 파일의 modules 속성을 통해 등록됩니다.

1
2
3
4
5
{
    "bizService": {
        "modules": [ "assemblyName", "MyBizLib.dll", "../legacy_biz/Biz*.dll" ]
    }
}

어셈블리 로드 문맥

비즈 모듈은 .net 어셈블리이며 초기화 시점에서 로드 되고 어셈블리에 포함된 비즈 클래스와 비즈 메서드들이 인덱싱됩니다. 위 modules 속성 값에서 assemblyName 과 같이 비즈 모듈이 어셈블리 이름으로 등록되면 어셈블리는 Assembly.Load 메서드를 통해 로드 되며, MyBizLib.dll 이나 와일드 카드가 사용된 ../biz/Biz*.dll 과 같이 .dll 파일 경로로 등록되면 Assembly.LoadForm 메서드를 통해 로드 됩니다.

Load 혹은 LoadFrom 메서서드를 통해 로드된 어셈블리는 운영체제에 의해 .dll 파일이 잠기게(lock) 되어 .dll 파일에 대한 업데이트(삭제, 변경)는 불가능해 집니다. 비즈 모듈(어셈블리) 파일이 잠기면 개발 및 운영 상황에서 다양한 문제를 유발할 수도 있습니다. 개발 시 빌드를 수행하면 비즈 모듈 .dll 파일이 컴파일러에 의해 업데이트 되어야 합니다. 만약 Fox Biz Service 를 호스팅하는 앱(대개의 경우 IIS Express 혹은 Kartel 서버)이 구동 중이라면 .dll 파일은 잠긴 상태가 되며 컴파일러는 빌드를 실패하게 될 것입니다.

운영 중 라이브 업데이트를 해야하는 앱도 문제가 될 수 있습니다. 과거 .NET Framework 시절 ASP.NET 앱은 .dll 파일이 업데이트 되면 자동으로 ASP.NET 앱을 재시작 하는 기능을 제공했었습니다. 즉, 운영 중 비즈 모듈 업데이트가 가능했고 비즈 모듈이 변경되면 자동으로 웹 앱이 재시작되었습니다.하지만 최신 .net 버전들은 이러한 기능을 제공하지 않습니다. 따라서 운영 도중 비즈 모듈을 업데이트하려면 웹 서버 프로세스(w3wp.exe)를 정지하고 비즈 모듈을 업데이트 한 후 다시 웹 서버 프로세스를 재시작 해야 합니다.

Note

Azure 혹은 AWS 와 같은 클라우드 환경이나 Docker 와 같은 컨테이너 환경에서는 명시적인 배포 프로세스가 존재하며 서버 중복을 통해 배포 시에도 서비스가 중단되지 않도록 하는 메커니즘을 제공합니다. 하지만 모든 앱 환경에서 이러한 배포 프로세스를 활용할 수 있다는 보장을 하기는 어렵습니다.

Fox Biz Service 는 .net 에 새로이 추가된 어셈블리 로드 문맥(Assembly Load Context) 기능을 활용하여 비즈 모듈 파일이 잠기는 것을 피하며 비즈 모듈 파일이 변경되는 것을 감지하여 다시 로드 하는 기능을 제공합니다. Load 혹은 LoadFrom 메서드 대신 어셈블리 로드 문맥에 비즈 모듈을 로드하기 위해서는 Fox Biz Service 구성 설정에서 useLoadContext 속성을 true 로 지정해야 합니다.

1
2
3
4
5
{
    "bizService": {
        "useLoadContext": true
    }
}

useLoadContext 속성이 true 로 지정되면 Fox Biz Service 는 어셈블리 로드 문맥을 생성하고 여기에 비즈 모듈을 스트림화 하여 로드(LoadFromStream 메서드) 합니다. 그 결과 비즈 모듈 파일은 잠기지 않으며 업데이트가 가능한 상태가 됩니다.

수집 가능한 로드 문맥

어셈블리 로드 문맥(Assembly Load Context)은 로드된 어셈블리를 언로드하는 기능을 제공합니다. 언로드 가능한 로드 문맥을 수집 가능한(collectible) 로드 문맥이라 부르며 명시적으로 로드 문맥에 로드된 모든 어셈블리를 언로드 할 수 있습니다.

Fox Biz Service 도 수집 가능한 로드 문맥을 위한 isCollectible 구성 설정 속성을 제공합니다. 이 속성이 true 로 지정되면 Fox Biz Service 는 로드 문맥에 비즈 모듈을 로드 할 때 FileSystemWatcher 객체를 설정하여 비즈 모듈 파일의 변경 사항을 감시 합니다. 만약 비즈 모듈이 변경되면 기존 로드 문맥을 언로드 하고 새로이 업데이트된 비즈 모듈을 다시 로드 하게 됩니다.

비즈 모듈이 언로드 되고 다시 로드되는 과정은 단순하지 않습니다. 예를 들어, 수집 가능한 로드 문맥에 로드 된 비즈 모듈이 다른 로드 문맥의 어셈블리에서 참조 중이라면 로드 문맥의 언로드는 이 참조가 없어질 때까지 언로드 되지 않습니다. 물론 Fox Biz Service 는 다시 로드 된 업데이된 비즈 모듈을 참조하기 때문에 새로운 비즈 메서드 호출은 업데이트된 비즈 모듈의 메서드가 호출되게 됩니다. 하지만 기존 비즈 모듈은 아직 언로드가 수행되지 않았을 수 있습니다. 따라서 isCollectible 속성을 true 로 지정하기 위해서는 비즈 모듈 간의 의존성을 최소화 하도록 설계하고 구현해야 합니다.

Note

수집 가능한 어셈블리 로드 문맥 사용시 고려해야 할 다양한 참조 관계와 형 변환 이슈 등에 대한 상세한 내용은 MSDN 의 How to use and debug assembly unloadability in .NET을 참조 하십시요.

Fox Biz Service 가 비즈 모듈 변경을 감지하면 기존 모듈을 언로드 하고 다시 로드하는 과정을 로그에 표시하며 다음은 그 예를 보여 줍니다.

1
2
3
4
5
6
[FoxBizServiceConfig] #00001:00000000#    Unloading modules... dir=./legacy_biz/  pattern=Biz*.dll
[FoxBizServiceConfig] #00001:00000000#    Reloading modules... name=group1  dir=./legacy_biz/  pattern=Biz*.dll
[FoxBizServiceConfig] #00001:00000000#    Found 1 files by file pattern: ./legacy_biz/\Biz*.dll
[FoxBizServiceConfig] #00001:00000000#    Loading(LoadContext) biz module: D:\Work\repos\neodeex\Samples\webapi\bizservice_overview\bizservice_app\bin\Debug\net8.0\legacy_biz\BizLib3.dll
[FoxBizServiceConfig] #00001:00000000#        Loading assembly successfully: BizLib3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
[FoxBizServiceConfig] #00001:00000000#        Biz class loaded : BizLib3.BizLogic3, 6 biz methods, id=BizLib3.BizLogic3

모듈 그룹

어셈블리 로드 문맥 사용 여부(useLoadContext)나 수집 가능성(isCollectible)을 설정하는 속성들은 modules 속성이 로드 하는 전체 비즈 모듈에 영향을 줍니다. 하지만 모든 비즈 모듈이 런타임에 업데이트를 허용할 필요가 없을 수도 있습니다. 특히, 수집 가능한 로드 문맥 기능을 활용하기 위해서는 파일 시스템을 감시하는 FileSystemWatcher 객체를 사용해야 하므로 시스템 자원을 소비하기 때문입니다.

Fox Biz Service 는 비즈 모듈을 그룹으로 나누어 각 그룹별로 이러한 설정을 지정할 수도 있습니다. 비즈 모듈 그룹은 modules 속성 대신 moduleGroups 컬렉션에 비즈 모듈을 명시하면 됩니다. modules 속성과 moduleGroups 속성은 모두 비즈 모듈을 나열하는데 사용할 수 있으며 다음 구성 설정은 그 예를 보여 줍니다.

{
    "bizService": {
        "useLoadContext": true,
        "modules": [ "assemblyName", "MyBizLib.dll" ],
        "moduleGroups": {
            "groupName1": {
                "directory": "../biz",
                "pattern": "Biz*.dll",
                "useLoadContext": true,
                "isCollectible": true
            },
            "groupName2": {
                "directory": "../legacy_biz/",
                "pattern": "OldModule.dll",
            }
        }
    }
}

moduleGroups 컬렉션은 이름으로 구분되는 여러 모듈 그룹 정의를 가질 수 있으며 각 모듈 그룹은 비즈 모듈 .dll 파일들에 대한 디렉터리(directory), 파일 이름 혹은 파일 패턴(pattern), 어셈블리 로드 문맥 사용 여부(useLoadContext), 수집 가능 여부(isCollectible)를 지정할 수 있습니다.

위 구성 설정 예제에서 modules 속성을 통해 로드 되는 2개의 비즈 모듈은 로드 문맥만을 사용하고 언로드를 허용하지 않습니다. 한편 groupName1 의 모듈 그룹들은 로드 문맥을 사용하며 언로드 역시 가능하지만 groupName2 모듈 그룹은 로드 문맥을 사용하지 않습니다.

ExecuteMultiple 메서드

ExecuteMultiple 메서드는 여러 개의 비즈니스 로직을 한 번에 호출할 수 있는 기능을 제공합니다. 이를 통해 네트워크 호출 횟수를 줄이고, 성능을 향상시킬 수 있습니다. 클라이언트는 호출하고자 하는 여러 비즈니스 로직에 대한 호출 정보를 여러 FoxBizRequest 객체에 담고, 이들을 FoxBizRequestCollection 컬렉션에 담아 ExecuteMultiple 메서드에 전달하면 됩니다. ExecuteMultiple 메서드는 FoxBizRequestCollection 컬렉션에 포함된 비즈니스 로직 호출들을 순차적으로 처리하며, 그 결과를 FoxBizResponseCollection 컬렉션으로 반환합니다. 반환된 FoxBizResponseCollection 컬렉션은 여러 개의 FoxBizResponse 객체를 포함할 수 있으며, 각 FoxBizResponse 객체는 하나의 비즈니스 로직 처리 결과를 나타냅니다.

다음 코드는 ExecuteMultiple 메서드를 사용하여 두 개의 비즈니스 로직을 배치로 수행하는 예를 보여줍니다.

1
2
3
4
5
6
7
8
FoxBizRequest request1 = new("BizLib1.TxBizLogic", "InsertTxData");
request1.Parameters["pk"] = 9;
request1.Parameters["data"] = "Test Data #999";
FoxBizRequest request2 = new("BizLib1.TxBizLogic", "GetTestData");
FoxBizRequestCollection requests = [request1, request2];
FoxBizServiceClient client = new("api/bizservice");
FoxBizResponseCollection responses = await client.ExecuteMultipleAsync(requests);
DataSet ds = (DataSet)(responses[1].Result);

Warning

Fox Data Service 트랜잭션 지원과 달리 Fox Biz Service 는 트랜잭션을 지원하지 않습니다. 트랜잭션 처리는 비즈니스 로직의 고유 기능이기 때문입니다. 따라서 ExecuteMultiple 메서드를 사용하여 여러 개의 비즈니스 로직을 호출하더라도, 각 비즈니스 로직은 독립적으로 실행되며, 하나의 비즈니스 로직이 실패하더라도 다른 비즈니스 로직의 실행에는 영향을 미치지 않습니다. 트랜잭션 처리가 필요한 경우, 트랜잭션을 묶는 비즈니스 로직을 별도로 구현하여 사용해야 합니다.

Fox Transaction 지원

Fox Biz Service 는 Fox Data Service 와 달리 트랜잭션 기능을 제공하지 않습니다. 트랜잭션 처리는 비즈니스 로직의 고유 기능이기 때문에 Fox Biz Service 에서 트랜잭션 처리 기능을 제공하는 것이 오히려 문제를 유발할 수 있기 때문입니다. 따라서 ExecuteMultiple 메서드를 사용하여 여러 개의 비즈니스 로직을 호출하더라도, 각 비즈니스 로직은 독립적으로 실행됩니다. 트랜잭션 처리가 필요한 경우, 트랜잭션을 묶는 비즈니스 로직을 별도로 구현하여 사용해야 합니다.

비즈니스 로직(비즈 메서드)에서 System.Transactions 를 사용하여 수동으로 트랜잭션을 처리할 수도 있지만 Fox Transactions 의 자동 트랜잭션 기능을 사용하면 보다 편리하게 선언적으로 트랜잭션을 처리할 수 있습니다. Fox Transactions 기능은 비즈니스 로직(비즈 메서드)에서 자동으로 트랜잭션이 시작되고 예외 발생 여부에 따라 트랜잭션이 자동으로 커밋되거나 롤백됩니다. Fox Transactions 기능의 기본적인 사용법은 관련 문서를 참고 하십시요.

// FoxBizService 는 TxBizLogic.InsertTxData 메서드를 호출하기 위해 
// 자동으로 수행 문맥을 생성
[FoxBizClass]
[FoxTransaction(FoxTransactionOption.Required)]
public class TxBizLogic : FoxBizBase
{
    [FoxBizMethod]
    public void InsertTxData(string data)
    {
        using TxDataAccess dac = new();
        // Fox Transactions 기능을 사용하는 InsertTestData 메서드를 호출하기 위해
        // 수동으로 수행 문맥 생성 필요
        ITxDataAccess itf = dac.CreateExecution<ITxDataAccess>();
        itf.InsertTestData(data);
    }

    ......
}

Fox Transactions 의 자동 트랜잭션 처리를 위해서는 수행 문맥(Execution Context)을 생성하고 수행 문맥을 통해 비즈니스 로직을 호출 해야 합니다. 하지만 Fox Biz Service 는 호출 대상 비즈니스 로직이 Fox Transactions 기능을 사용하는 경우 자동으로 수행 문맥을 생성하여 비즈니스 로직을 호출합니다. 따라서 Fox Biz Service 를 통해 비즈니스 로직을 호출하는 경우, 개발자는 수행 문맥을 직접 생성할 필요가 없습니다. 다만, 비즈니스 로직 메서드 내에서 다른 Fox Transactions 기능을 사용하는 메서드를 호출하는 경우에는 수행 문맥을 직접 생성해야 합니다.

예외 처리

Fox Biz Service 가 데이터베이스 액세스 중에 발생하는 예외는 FoxBizServiceException 예외로 래핑되어 호출자에게 전달됩니다. FoxBizServiceException 예외는 InnerException 속성에 실제 예외를 포함합니다. 따라서 호출자는 FoxBizServiceException 예외를 캐치하여 InnerException 속성에서 실제 예외를 확인할 수 있습니다.

예외 처리에 대한 상세한 예제 코드는 다음을 참고 하십시요.

기본 예외 처리

Fox Biz Service 가 실제 예외 타입 대신 FoxBizServiceException 타입을 사용하는 이유는 일관된 예외 처리 모델을 제공하기 위함입니다. Fox Biz Service 를 호출하는 코드는 FoxBizServiceException 타입을 catch 하는 코드로 비즈니슬 로직을 수행하면서 발생하는 절대 다수의 예외를 처리할 수 있습니다.

하지만 여러 차례 언급했듯이 Fox Biz Service 는 대부분 Fox Web API 나 gRPC 와 같은 통신 서비스 계층에서 호출되기 때문에 개발자가 직접 FoxBizServiceException 을 처리하는 경우는 매우 드뭅니다. 대신 FoxBizServiceException 의 예외 정보를 포함하는 DTO 객체로 서비스 오류 정보 객체, 즉 FoxServiceExceptionInfo 객체의 정보를 활용하게 됩니다. 또한 Fox Web API 클라이언트 라이브러리를 사용하는 경우에는 클라이언트가 발생하는 FoxRemoteServiceException 타입의 ServiceInfo 속성을 통해 비즈니스 로직에서 발생한 예외의 타입, 오류 메시지, 호출 스택 등을 확인하게 됩니다.

서비스 오류 정보 객체

Fox Web API 와 같은 통신 서비스에서 Fox Biz Service 를 호출할 때, FoxBizServiceException 예외는 통신 서비스의 예외 처리 모델과 호환되는 방식으로 변환되어 원격 클라이언트에게 전달될 수 있습니다. 예를 들어, ASP.NET Web API 에서 예외 필터를 통해 JSON 혹은 XML로 변환하여 예외 정보를 클라이언트에게 반환할 수 있습니다.

물론 Fox Web API 는 FoxBizServiceException 예외를 처리하는 예외 필터를 이미 제공하며 JSON 형태의 예외 정보를 원격 클라이언트에게 제공합니다. FoxServiceErrorInfo 클래스는 예외 정보를 담는 DTO(Data Transfer Object) 역할을 하며 FoxBizServiceException 예외에 포함된 예외 메시지, 호출 스택 등 정보는 이 DTO를 사용하여 JSON 으로 직렬화하여 클라이언트에게 반환합니다.

Fox Biz Service 의 전형적인 사용 시나리오는 원격 클라이언트가 Fox Web API 를 통해 서비스를 호출하는 방식이므로 개발자는 FoxBizServiceException 예외를 직접 처리할 필요가 거의 없습니다. 대신 FoxServiceErrorInfo 객체에 포함된 정보를 더 많이 다루게 될 것입니다.

다음 예제 코드는 Fox Web API 클라이언트가 Fox Biz Service 에서 발생된 예외를 처리하는 예를 보여줍니다. Fox Web API 클라이언트는 서버측에서 발생된 예외를 나타내는 FoxRemoteServiceException 예외를 발생시킵니다. 이 예외 객체의 ErrorInfo 속성은 서비스 측 예외 정보를 포함하는 FoxServiceErrorInfo 객체를 반환합니다.

FoxBizRequest request = new("BizLib3.BizLogic3", "DivideByZero");
FoxBizServiceClient client = new("api/bizservice");
Console.Write("Service Result: ");
try
{
    FoxBizResponse response = client.Execute(request);
    Console.WriteLine($"{response.Result}");
}
catch(FoxRemoteServiceException ex)
{
    string exceptionType = ex.ErrorInfo!.ExceptionType;
    string stackTrace = ex.ErrorInfo!.StackTrace;
    Console.WriteLine(ex.Message);
    Console.WriteLine($"ExceptionType:{exceptionType}");
    Console.WriteLine($"StackTrace:\n{stackTrace}");
}
// 수행 결과
//   Service Result: Attempted to divide by zero. (HTTP 500)
//   ExceptionType: NeoDEEX.ServiceModel.Services.Biz.FoxBizServiceException
//   StackTrace:
//     at BizLib3.BizLogic3.DivideByZero()
//     ...... 생략 ......

FoxServiceErrorInfo 객체와 접근 방법에 대한 상세한 내용은 Fox Web API 클라이언트 문서를 참고 하십시요.

예외 전파 제어

Fox Biz Service 는 비즈 메서드 수행 중에 예외가 발생하는 경우, 기본적으로 해당 예외를 FoxBizServiceException 예외로 래핑하여 호출자에게 전파(propagate) 합니다. 하지만 FoxBizRequest 객체의 ThrowException 속성에 false 값이 지정된 경우 예외를 전파하는 대신 예외 정보(FoxServiceErrorInfo 객체)를 FoxBizResponse 객체의 ErrorInfo 속성에 포함하여 반환합니다. 다음 예제 코드는 원격 클라이언트가 Fox Web API 를 통해 Fox Biz Service 를 호출할 때 ThrowException 속성에 false 값을 지정하여 예외를 전파하지 않고 예외 정보를 반환받는 예제 코드입니다.

1
2
3
4
5
6
7
8
9
FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequest request = new("BizLogic", "ErrorMethod");
request.ThrowException = false;
FoxBizResponse response = client.Execute(request);
if (!response.Success)
{
    string errorMessage = response.ErrorInfo?.Message ?? "Unknown error";
    ......
}

이 경우, 호출자는 FoxBizResponse 객체의 Success 속성을 확인하여 쿼리의 성공여부를 먼저 파악하고 쿼리가 실패한 경우 ErrorInfo 속성에서 예외 정보를 확인할 수 있습니다.

ThrowException 속성은 Execute 메서드들에서 단일 쿼리를 수행하는 경우 뿐만 아니라 ExecuteMultiple 메서드에서 여러 쿼리를 배치로 수행하는 경우에도 적용됩니다. ExecuteMultiple 메서드에서는 FoxBizRequestCollection 객체의 ThrowException 속성에 false 값이 지정된 경우, 예외가 발생한 경우 FoxBizResponseCollection 객체에서 Success 속성을 false 를 설정하고 ErrorInfo 속성에서 예외를 발생한 FoxBizRequest 객체의 인덱스를 포함하는 예외 정보를 확인할 수 있습니다.

FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequestCollection requests = CreateResponseCollection();
requests.ThrowException = false;
FoxBizResponseCollection responses = client.ExecuteMultiple(requests);
if (!responses.Success)
{
    int errorRequestIndex = responses.ErrorInfo.Index;
    string errorMessage = responses.ErrorInfo?.Message ?? "Unknown error";
    ......
}

위 예제 코드에서 responses 컬렉션은 2개의 FoxBizResponse 객체를 포함하며 첫 번째 FoxBizResponse 객체는 성공적으로 수행된 쿼리의 결과를 포함하고, 두 번째 FoxBizResponse 객체는 실패한 쿼리의 예외 정보를 포함합니다. 호출자는 responses.ErrorInfo.Index 속성을 확인하여 어떤 쿼리에서 예외가 발생했는지 확인할 수 있습니다.

Continue On Error

ExecuteMultiple 메서드는 여러 개의 비즈니스 로직을 배치로 수행하는 메서드이므로, 여러 비즈 메서드 중 일부 메서드가 실패하는 경우에도 나머지 비즈 메서드를 계속 수행하도록 제어할 수 있습니다. 이를 위해 FoxBizRequestCollection 객체의 ContinueOnError 속성을 사용합니다. ThrowException 속성이 false 이고 ContinueOnError 속성에 true 값이 지정된 경우, 예외가 발생한 비즈 메서드에 대한 예외 정보가 해당 FoxBizResponse 객체의 ErrorInfo 속성에 포함되어 반환되며, 나머지 비즈 메서드들은 계속 수행됩니다.

FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequestCollection requests = CreateResponseCollection();
requests.ThrowException = false;
requests.ContinueOnError = true;
FoxBizResponseCollection responses = client.ExecuteMultiple(requests);
foreach(FoxBizResponse response in responses)
{
    if (response.Success)
    {
        // ... 성공한 쿼리 결과 처리 (생략) ...
    }
    else
    {
        string errorMessage = response.ErrorInfo?.Message ?? "Unknown error";
        // ... 실패한 쿼리 결과 처리 (생략) ...
    }
}

로깅

Fox Biz Service 는 Fox Data Service 와 달리 사용자 작성 코드에 대해 디버깅이 가능하므로 오류 발생시 추적과 진단이 상대적으로 용이합니다. 그럼에도 불구하고 Fox Biz Service 는 초기화 시점부터 어떤 비즈 모듈이 로드 되었는지를 비롯하여 비즈 메서드의 수행 단계 마다 상세한 로그를 남깁니다. 이러한 로그는 Fox Biz Service 가 비즈 모듈을 수행하는 도안 발생하는 여러 가지 상황을 추적하고 진단하는데 유용하게 사용될 수 있습니다.

서비스 로깅

Fox Biz Service 는 FoxBizService 클래스의 LoggerName 속성 혹은 구성 설정의 "bizService:diagnostics:loggerName" 속성이 지정하는 로거에 로그를 기록합니다. LoggerName 속성의 디폴트 값은 NeoDEEX.ServiceModel.Biz.FoxBizService 이지만 이 속성이 지정하는 로거가 등록되지 않은 경우 로그는 기록되지 않습니다.

여러 차례 언급한바 있지만 개발자가 직접 FoxBizService 클래스의 인스턴스를 생성하여 Fox Biz Service 를 호출하는 경우는 매우 적습니다. 대신 Fox Web API 와 같은 통신 서비스에서 Fox Biz Service 를 사용하여 클라이언트의 요청을 처리하는 경우가 많습니다. 이러한 경우 ASP.NET Web API 와 같은 통신 서비스 앱의 구성 설정 파일에서 "bizService:diagnostics:loggerName" 속성을 설정하여 서버 측 로깅을 활성화할 수 있습니다.

1
2
3
4
5
6
7
{
  "bizService": {
    "diagnostics": {
        "loggerName": "MyLogger"
    }
  }
}

다음은 활성화된 서비스 로깅에 대한 예를 보여줍니다. 호출자 정보를 포함하여 비즈 클래스 ID, 비즈 메서드 ID, 매개변수 개수, 비즈 메서드 수행 시간 등에 대한 로그가 기록됩니다.

1
2
3
4
I 2026-10-19 21:34:49.75005 [BizService] #00008:39015791#>> Biz Service Start: Execute  Requests=1  UserId=  ClientMachine=NEOWORK2  ClientIP=192.168.219.101;172.28.32.1  ClientMAC=A0-36-BC-B8-62-C7;0-15-5D-EF-FC-42
V 2026-10-19 21:34:49.75098 [BizService] #00008:39015791#      Before Execute BizMethod: ClassId=BizLogic  MethodId=Echo  Type=serviceapp.biz.BizLogic  Method=Echo  in-params=1
V 2026-10-19 21:34:49.75231 [BizService] #00008:39015791#      After Invoke: elpased = 1.0 msec, out-params:0
I 2026-10-19 21:34:49.75281 [BizService] #00008:39015791#>> Biz Service End: Execute  elapsed = 2.9 msec

로그 ID

Fox Biz Service 가 사용되면 다수의 클라이언트로부터 동시에 여러 요청이 들어오며 이들은 동시에 처리됩니다. 따라서 Fox Biz Service 가 생성하는 로그는 여러 클라이언트 요청에 대한 로그가 섞여서 기록될 수 있습니다. 어떤 비즈니스 로직에 성능 문제나 오류가 발생한 경우 해당 비즈 메서드와 관련된 로그를 식별하는 것이 중요합니다. 하지만 동시에 처리되는 여러 요청들을 처리하는 Fox Biz Service 는 동시에 수 많은 로그 항목을 생성하기 때문에 어떤 로그 항목이 어느 요청을 처리하면서 기록된 것인지 손쉽게 파악할 방법이 필요합니다.

Fox Biz Service 에 대한 로그는 로그 ID 라는 식별자를 포함합니다. 로그 ID 는 # 문자로 구분된 두 개의 8자리 16진수 숫자로 구성됩니다. 예를 들어, 위 예시 로그에서 #00008:39015791# 부분이 로그 ID 입니다. 로그 ID 는 Fox Biz Service 가 하나의 클라이언트 요청을 처리하는 동안 생성되는 모든 로그에 포함됩니다. 따라서 클라이언트 요청과 관련된 모든 로그를 쉽게 식별할 수 있습니다.

Warning

로그 ID 는 GUID 와 같은 고유 식별자가 아닙니다. 로그 ID 는 스레드 ID 와 FoxBizService 인스턴스 식별자의 조합이기 때문에 특정 서버에서 일정 기간 내에서만 고유합니다. 따라서 로그 ID 를 긴 시간동안 보관된 로그에서 특정 클라이언트 요청과 관련된 로그를 식별하는데 사용하는 것은 적절하지 않습니다. 로그 ID 는 동시 호출이 발생하는 상황에서 하나의 요청을 처리하는 로그를 구별하는 용도로 사용하는 것이 적절합니다.

동시에 여러 클라이언트가 Fox Biz Service 에 요청을 보내는 경우, 각 클라이언트 요청에 대해 고유한 로그 ID 가 생성되어 각 요청과 관련된 로그를 구분할 수 있습니다. 또한 하나의 클라이언트가 여러 개의 요청을 보내는 경우에도 각 요청에 대해 고유한 로그 ID 가 생성됩니다. 로그 ID 는 Fox Biz Service 가 클라이언트 요청을 처리하는 동안 생성되는 모든 로그에 포함되므로, 클라이언트 요청과 관련된 모든 로그를 쉽게 식별할 수 있습니다.

로그 ID 는 클라이언트의 요청에 의해 클라이언트에게 반환될 수도 있습니다. 클라이언트는 FoxBizRequest 객체의 Diagnostics 속성에 FoxBizRequestDiagnostics.LogId 플래그를 설정하여 클라이언트 요청과 관련된 로그 ID 를 FoxBizResponse 객체의 LogId 속성에 포함하여 반환하도록 할 수 있습니다. 클라이언트는 반환된 로그 ID 를 사용하여 서버 측 로그에서 해당 클라이언트 요청과 관련된 로그를 쉽게 식별할 수 있습니다.

다음 코드는 Fox Web API 클라이언트가 Fox Biz Service 를 호출할 때 FoxBizRequest 객체의 Diagnostics 속성에 FoxBizRequestDiagnostics.LogId 플래그를 설정하여 클라이언트 요청과 관련된 로그 ID 를 FoxBizResponse 객체의 LogId 속성에 포함하여 반환하도록 하는 예제 코드입니다.

1
2
3
4
5
6
FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequest request = new("BizLogic", "Echo");
request.Parameters["input"] = "Hello World!";
request.Diagnostics = FoxBizRequestDiagnostics.LogId;
FoxBizResponse response = client.Execute(request);
Console.WriteLine($"Log ID: {response.LogId}");

Warning

서비스 로그, 로그 ID, 성능 정보 등과 같은 서버 측 정보는 민감한 정보가 될 수 있으므로 Web API 설정에서 FoxWebApiServerConfig.ReturnServerInfo 속성이 true 가 아닌 경우에는 Web API 수준에서 필터링되어 JSON 으로 직렬화되지 않아, 클라이언트에게 반환되지 않습니다.

1
2
3
4
5
{
  "webApiServer": {
    "returnServerInfo": true
  }
}

ExecuteMultiple 메서드에서 여러 비즈니스 로직을 수행하는 경우, 각 비즈 메서드마다 개별 로그 ID 가 생성되지 않고 전체 요청을 처리하는 것에 대해 하나의 로그 ID 가 생성됩니다. 따라서 ExecuteMultiple 메서드에서 여러 비즈 메서드를 수행하는 경우, FoxBizRequestCollection 객체의 Diagnostics 속성을 통해 로그 ID 요청을 해야 하며 FoxBizResponseCollection 객체의 LogId 속성에 로그 ID 가 포함되어 반환됩니다. 컬렉션에 포함된 각 FoxBizResponse 객체의 LogId 속성은 로그 ID 를 포함하지 않고 null 값을 반환합니다.

요청 시 로깅

서비스 로그는 서버 측에서 로거를 사용하여 기록하는 로그 입니다. 반면 "요청 시 로깅"은 클라이언트가 로그를 요청하면 쿼리를 수행하면서 발생하는 로그를 클라이언트에게 반환하는 기능입니다. 요청 시 로깅을 활성화하려면, FoxBizRequest 객체의 Diagnostics 속성에 FoxBizRequestDiagnostics.ServiceLog 플래그를 설정해야 합니다. 요청 시 로깅이 활성화된 경우, 쿼리를 수행하면서 발생하는 로그가 FoxBizResponse 객체의 ServiceLog 속성(string[] 타입)에 포함되어 반환됩니다.

1
2
3
4
5
6
7
FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequest bizRequest = new("biz_class_id", "biz_method_id")
{
    Diagnostics = FoxBizRequestDiagnostics.ServiceLog
};
FoxBizResponse bizResponse = client.Execute(bizRequest);
string[] serviceLog = bizResponse.ServiceLog;

요청 시 로깅은 구성 설정의 "bizService:diagnostics:enable" 속성이 true 인 경우에 작동하며 서비스 로그 기록 여부와 무관하게 작동합니다. 동시 사용자가 많은 서비스 환경에서 서버에 부하를 주지 않고 특정 요청에 대해서만 로그를 추적할 수 있기 때문에 트러블 슈팅이나 디버깅에 크게 도움이 될 수 있습니다.

Warning

서비스 로그 정보 역시 민감한 정보가 포함될 수 있으므로 Web API 설정에서 ReturnServerInfo 속성이 true 가 아닌 경우에는 Web API 수준에서 필터링되어 JSON 으로 직렬화되지 않아, 클라이언트에게 반환되지 않습니다.

로그 ID 기능과 동일하게 ExecuteMultiple 메서드 호출에 대해 요청 시 로깅 기능을 활성화 하려면 FoxBizRequestCollection 객체의 Diagnostics 속성에 FoxBizRequestDiagnostics.ServiceLog 플래그를 설정해야 합니다. ExecuteMultiple 메서드를 수행하면서 생성되는 로그 항목은 FoxBizResponseCollection 객체의 ServiceLog 속성에 포함되어 반환되며, 컬렉션에 포함된 각 FoxBizResponse 객체의 ServiceLog 속성에는 로그 항목이 포함되지 않습니다.

성능 측정

Fox Biz Service 는 비즈니스 로직을 수행하면서 주요 처리 과정에 대한 성능 측정을 수행합니다. 이렇게 측정된 성능 정보는 로그를 통해 표시됩니다. 예를 들어, ExecuteMultiple 메서드가 여러 비즈니스 로직을 수행하는 동안 개별 비즈니스 로직에 소요된 시간 소요된 시간과 컬렉션 내의 전체 쿼리들을 모두 수행하는데 소요되는 시간을 모두 측정하여 로그에 표시합니다.

 >> Biz Service Start: ExecuteMultiple  Requests=2  UserId=  ClientMachine=  ClientIP=  ClientMAC=
     Start Processing Request[0]
       Before Execute BizMethod: ClassId=BizLogic  MethodId=Echo  Type=serviceapp.biz.BizLogic  Method=Echo  in-params=1
       After Invoke: elpased = 0.0 msec, out-params:0
     End Processing Request[0]  elapsed = 0.2 msec
     Start Processing Request[1]
       Before Execute BizMethod: ClassId=BizLogic  MethodId=HelloWorld  Type=serviceapp.biz.BizLogic  Method=HelloWorld  in-params=0
       Hello Biz Service World !
       After Invoke: elpased = 0.0 msec, out-params:0
     End Processing Request[1]  elapsed = 0.2 msec
 >> Biz Service End: ExecuteMultiple  elapsed = 0.7 msec

로그 뿐만 아니라 매 호출마다 반환하는 FoxBizResponse 객체의 ElapsedMilliseconds 속성에 쿼리 수행에 소요된 시간을 밀리초 단위로 포함하여 반환합니다. 서비스 수행 시간은 EnableDiagnostics 속성값이나 EnablePerfLog 속성값과 무관하게 항상 측정되며 항상 클라이언트에게 반환됩니다.

1
2
3
4
FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequest bizRequest = new("my_class_id", "my_method_id");
FoxBizResponse bizResponse = client.Execute(bizRequest);
double elapsed = bizResponse.ElapsedMilliseconds;

ExecuteMultiple 메서드는 FoxBizResponseCollection 객체의 ElapsedMilliseconds 속성에 모든 비즈니스 로직들을 수행하는데 소요되는 시간을 반환하며, 컬렉션에 포함된 각 FoxBizResponse 객체의 ElapsedMilliseconds 속성에는 개별 비즈니스 로직을 수행하는데 소요된 시간이 반환됩니다.

성능 문맥 로그

단순한 로그 메시지 뿐만 아니라 성능 문맥 기능(Fox Performance Context)과 연계하여 쿼리 수행에 대한 다양한 정보를 포함하는 성능 로그를 기록할 수도 있습니다. 성능 로그는 EnablePerfLog 속성이 true로 설정된 경우 활성화되며 측정된 성능 문맥 정보는 PerfLoggerName 속성이 지정하는 로거에 기록됩니다. 다음은 구성 설정을 통해 EnablePerfLog 속성을 true로 설정하고 성능 로그를 기록하는 로거 이름을 지정하는 예제입니다.

{
  "bizService": {
    "perfLog": {
      "enable": true,
      "loggerName": "PerfLogger2"
    }
  },
  "logging": {
    "PerfLogger2": {
      "providerType": "NeoDEEX.Diagnostics.Loggers.FoxTextFileLoggerProvider"
    }
  }
}

다음은 앞서 ExecuteMultiple 메서드 서비스 로그 예제에서 EnablePerfLog 속성을 true로 설정하여 성능 로그를 활성화한 경우의 예시 로그입니다. ExecuteMultiple 메서드가 전체적으로 0.66 밀리초(msec)가 소요되었으며, 이 메서드 내에서 호출된 Execute 메서드들의 수행 시간과 각 쿼리 수행에 대한 비즈 메서드의 수행시간도 포함된 로그 입니다.

1
2
3
4
5
6
I 2026-10-24 19:38:06.39508 [PerfLogger2] Performance Activity [NEOWORK2:17668:FoxBizService;BizLogicLayer] elapsed=3.52 time=19:38:06:3914 id=20fa9b2e-93fa-4991-87be-25df51629943
    ExecuteMultiple inclusive=3.52 exclusive=1.29 time=19:38:06:3914 id=6 parent=-1
        Execute inclusive=2.09 exclusive=0.76 time=19:38:06:3922 id=7 parent=6
            Dispatch-Echo inclusive=1.33 exclusive=1.33 time=19:38:06:3928 id=8 parent=7
        Execute inclusive=0.15 exclusive=0.03 time=19:38:06:3944 id=9 parent=6
            Dispatch-HelloWorld inclusive=0.12 exclusive=0.12 time=19:38:06:3944 id=10 parent=9

Note

서비스 로그에 표시된 수행 시간은 반올림된 소수점 한 자리만 표시합니다. 한편 성능 문맥 로그는 소수점 두자리까지만 표시합니다. 하지만 실제 성능 문맥 정보는 성능 문맥 정보 객체(FoxPerformanceActivityInfo 객체)에 기록되며 이 객체 내에는 성능 측정시 double 타입의 수행 시간 정보를 포함하고 있습니다.

요청 시 성능 정보

Fox Biz Service 는 요청 시 로깅(FoxBizRequestDiagnostics.ServiceLog) 기능과 유사하게 클라이언트 요청에 따라 수행하는 쿼리 수행에 대한 성능을 측정하여 반환하는 기능도 제공합니다. FoxBIzRequest 객체의 Diagnostics 속성에 FoxBizRequestDiagnostics.PerformanceInfo 플래그를 설정하여 서비스가 비즈니스 로직을 수행할 때 측정되는 성능 문맥 정보를 반환하도록 요청할 수 있습니다. 요청이 활성화된 경우, 비즈니스 로직을 수행하면서 측정된 성능 정보가 FoxBizResponse 객체의 PerformanceInfo 속성에 포함되어 반환됩니다.

PerformanceInfo 속성은 FoxPerformanceActivityInfo 객체로, 쿼리 수행에 대한 성능 문맥 정보를 계층 구조로 포함하고 있습니다. 따라서 쿼리 수행에 대한 상세한 성능 정보를 확인할 수 있습니다. 앞서 살펴보았던 성능 로그는 성능 정보를 나타내는 FoxPerformanceActivityInfo 객체를 문자열로 표시한(ToString() 메서드) 결과입니다.

다음 코드는 Fox Web API 클라이언트가 FoxBizRequestCollection 객체의 Diagnostics 속성에 FoxBIzequestDiagnostics.PerformanceInfo 플래그를 설정하여 Fox Biz Service 에게 ExecuteMultiple 메서드를 수행하면서 성능 정보를 반환하도록 요청하는 예제 입니다.

1
2
3
4
5
6
7
8
FoxBizServiceClient client = new("/api/bizservice");
FoxBizRequest request1 = new("BizLogic", "Echo");
request1.Parameters["input"] = "Hello World!";
FoxBizRequest request2 = new("BizLogic", "HelloWorld");
FoxBizRequestCollection requests = [request1, request2];
requests.Diagnostics = FoxBizRequestDiagnostics.PerformanceInfo;
FoxBizResponseCollection responses = client.ExecuteMultiple(requests);
Console.WriteLine(responses.PerformanceInfo.ToString());

위 호출은 다음과 같은 성능 정보를 출력하게 됩니다. 이 결과는 성능 로그와 동일한 내용이며 Fox Biz Service 클라이언트가 서비스 측에서 생성되는 성능 로그와 동일한 성능 정보를 반환받는 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
Performance Activity [NEOWORK2:17668:FoxBizService;BizLogicLayer] elapsed=3.52 time=19:38:06:3914
id=20fa9b2e-93fa-4991-87be-25df51629943
    ExecuteMultiple inclusive=3.52 exclusive=1.29 time=19:38:06:3914 id=6 parent=-1
        Execute inclusive=2.09 exclusive=0.76 time=19:38:06:3922 id=7 parent=6
            Dispatch-Echo inclusive=1.33 exclusive=1.33 time=19:38:06:3928 id=8 parent=7
        Execute inclusive=0.15 exclusive=0.03 time=19:38:06:3944 id=9 parent=6
            Dispatch-HelloWorld inclusive=0.12 exclusive=0.12 time=19:38:06:3944 id=10 parent=9

성능 로그에 쌓이는 수많은 로그 항목들 중에서 성능 정보를 찾는 것보다 클라이언트에게 반환된 성능 정보를 통해 방금 호출한 쿼리의 성능 정보를 바로 확인할 수 있으므로, 성능 로그를 통해 성능을 확인하는 것보다 훨씬 편리합니다. 또한 성능 로그는 서버 측에서만 확인할 수 있지만, 요청 시 성능 정보는 클라이언트가 직접 확인할 수 있습니다.

Warning

요청 시 성능 정보는 성능 정보를 직렬화해야 하며 응답의 크기를 증가시키므로 네트워크 대역폭에 좋지 않은 영향을 줄 수 있습니다. 따라서 일반적인 상황에서는 이 기능을 사용하지 않는 것이 좋으며 개발 단계 혹은 성능 문제를 진단하는 상황에서만 이 기능을 사용하는 것이 좋습니다.

기타 기능

로깅 억제 플래그

Fox Biz Service 는 비즈니스 로직을 수행하면서 발생하는 로그를 기록하며 성능 정보를 수집합니다. 하지만 모든 요청에 대해 성능 정보를 수집하는 것이 필요한 것은 아닙니다. 예를 들어, 간단히 사용자 이름만 조회하는 쿼리나 다국어를 위해 문자열을 조회하는 쿼리는 굳이 성능 정보를 수집할 필요가 없을 수 있습니다.

너무 많은 성능 정보가 기록되지 않도록 성능 정보가 필요하지 않은 비즈니스 로직에 대해 FoxBizRequest 객체의 Diagnostics 속성에 SuppressPerfLogWrite 플래그를 설정하면 성능 정보가 로그에 기록되지 않도록 억제할 수 있습니다.

Note

Fox Biz Service 가 생성하는 서비스 측 로그는 억제되지 않습니다. 억제 플래그는 서버측에서 성능 로그가 기록되는 것을 억제할 뿐입니다. 따라서 억제 플래그를 설정하더라도 서비스 로그는 기록되며, 요청 시 로깅, 요청 시 성능 정보를 사용하여 필요한 정보를 획득하는 것도 가능합니다.

Summary

Fox Biz Service는 대규모 백엔드 시스템에서 비즈니스 로직을 손쉽게 호출하고 유지보수할 수 있도록 다양한 고급 기능을 제공합니다. 수백, 수천 개의 비즈니스 로직을 효율적으로 관리하기 위해 여러 비즈 모듈로 분리하여 등록하며, .NET의 어셈블리 로드 문맥(Assembly Load Context)과 수집 가능한(collectible) 로드 문맥 기능을 활용하여 DLL 파일 잠김 없이 런타임에 모듈을 동적으로 업데이트할 수 있습니다. 또한 모듈 그룹 설정을 통해 개별 모듈 단위로 어셈블리 로드 문맥 적용 여부나 파일 감시(isCollectible) 옵션을 다르게 지정함으로써 시스템 자원을 효율적으로 분배할 수 있습니다.

여러 비즈니스 로직을 순차적으로 한 번에 실행하는 ExecuteMultiple 메서드를 제공하여 네트워크 호출 오버헤드를 줄여주며, 트랜잭션 처리는 비즈니스 로직 고유의 역할로 분리하여 Fox Transactions의 자동 트랜잭션기능과 연동해 선언적으로 안전하게 관리합니다. 예외 발생 시에는 일관된 예외 처리 모델인 FoxBizServiceException 및 FoxServiceErrorInfo DTO 객체를 통해 원격 클라이언트까지 안전하게 예외 정보를 전달하며, ThrowException 이나 ContinueOnError 속성을 활용해 실패 시 예외 전파 여부나 나머지 비즈 메서드의 연속 수행 여부를 정교하게 제어할 수 있습니다.

마지막으로 서비스 로깅, 동시 호출 환경에서 개별 요청을 식별하는 로그 ID, 그리고 클라이언트가 직접 로그를 전달받는 요청 시 로깅 기능을 탑재하여 추적 및 진단 편의성을 극대화했습니다. 이에 더해 성능 문맥 로그와 요청 시 성능 정보를 활용하여 각 비즈 메서드 및 서비스 호출 전반에 걸친 수행 시간을 계층적으로 측정하고 모니터링함으로써, 개발 및 운영 단계에서 발생하는 성능 병목을 신속하게 진단할 수 있습니다.