자료/ASP.NET

[Microsoftware] Enterprise Library 2.0 <part2>

네오블루 2007. 12. 11. 20:31

월간 마이크로소프트웨어 2006년 8월호

----------------------------------------------------------------------------------------

 전사적 공통 엔진

Enterprise Library 2.0 (2)


한용희woom33@paran.com

롯데정보통신 정보기술연구소에 재직 중이며, 닷넷 기반의 여러 프로젝트에 참여했다. 현재 Microsoft Visual C# MVP이며 MSDN 세미나 강사로도 활동 중이다.

 

운영체제 │ 윈도우 2000, 윈도우 2003, 윈도우 XP
개발도구 │ Microsoft Visual Studio 2003/2005, SQL Server 2000/2005
기초지식 │ C#
응용분야 │ 모든 애플리케이션


 

작년 11월 닷넷 프레임웍 2.0이 발표되고 올 1월에는 공통 개발 프레임워크의 기본인 Enterprise Library 2.0이 발표되었다. 이제는 닷넷 기반의 프로젝트를 하는 개발자라면 기본적으로 엔터프라이즈 라이브러리 정도는 알고 있어야 하는 분위기로 흘러가고 있다. 이번 호에는 지난 호에 이어서 에외 처리, 로깅, 보안 애플리케이션 블록에 대해 살펴보자.


대학에서 프로그래밍 공부를 했다고 하더라도 막상 SI업계에 발을 디디게 되면, 모든 프로그램을 처음부터 프로그래밍 하지는 않는다. 대부분 회사에서는 개발 환경에 맞는 공통 프레임워크를 이미 구축해 놓고 있기 때문이다. 이처럼 프로그래밍의 기초가 되는 각종 언어나 개념들을 알고 있더라도 새로운 환경에 접하게 되면 당황스러울 때가 많다.

단순히 언어만 안다고 프로그래밍을 할 수 있는 것이 아니다. 제일 먼저 기초가 되는 개발 프레임워크부터 잘 알아야만 개발을 할 수가 있다. 그래서 프로젝트를 수행할 때, 먼저 공통 개발 프레임워크를 만들게 된다. 이어 이와 관련된 내용을 개발자들에게 교육한 후에 본격적으로 개발에 들어가게 된다. 이처럼 개발자들에게 필수적인 공통 개발 프레임워크의 주요 기능들을 몇 가지 열거해 보면 아래와 같다.

 

* 공통적인 기능을 묶어서 일관된 인터페이스로 제공할 것.
* 개발자들이 코딩을 좀 더 적게 하도록 할 것.
* 각종 로깅, 이벤트 정보를 기록에 자동으로 남기도록 할 것.
* 개발 환경 변화에 손쉽게 변화할 수 있도록 할 것.
* 비즈니스 요구 사항 변화에 쉽게 적응할 수 있도록 할 것.

 

이러한 요구사항을 가지고 공통 프레임워크를 만들게 되는데, 이 때 각 프로젝트마다 각 자의 요구사항이나 환경이 다르므로 별도의 프레임워크를 만들어서 사용한다. 그러므로 개발업체의 사정에 따라 공통 개발 프레임워크가 서로 다를 수가 있다.

이 같은 측면에서 마이크로소프트는 공통 개발 프레임워크의 개발을 보다 쉽게 하고, 통일성을 확보하기 위해서 Enterprise Library라는 제품을 무료 버전으로 공개하였다. 이 Enterprise Library는 모든 소스가 공개되어 있다. 개발자들이 자신들의 환경에 맞게 수정해서 쓰는 것을 권장하는 라이브러리인 셈이다. 또한 대다수 환경 설정 부분을 별도로 분리하였기 때문에 대부분의 개발환경이나 비즈니스 요구사항의 변화에도 쉽게 대처할 수 있다. 소스 코드의 수정 없이 환경설정 부분만 세팅해 주면 쉽게 변경해 사용할 수 있다.

지난 해 11월 닷넷 프레임워크 2.0이 공개된 이후, 수많은 닷넷 플랫폼 기반의 프로젝트들이 진행되고 있다. 프로젝트를 처리하다 보면, 공통 개발 프레임워크에 대한 고민이 뒤따르게 된다. 하지만 이 같은 개발자들의 우려도 올해 1월 Enterprise Library 2.0이 발표된 이후 상당 부분 해결이 된 상태다. 이제  닷넷 기반의 프로젝트를 수행하는 개발자라면, 공통 개발 프레임워크는 기본적으로 알고 있어야 하는 분이기로 흘러가고 있다.

지난 호에서 Enterprise Library 2.0의 6개의 블록들 가운데 데이터 액세스, 캐싱, 암호 애플리케이션 블록들에 대해 소개를 하였다. 이번 호에는 나머지 3개 블록인 예외 처리, 로깅, 보안 애플리케이션 블록에 대해 소개하려고 한다.

 

예외 처리 애플리케이션 블록(Exception Handling Application Block)

예외 처리를 하는 데 있어 외부에 유출되어서는 안 될 특정 예외 정보는 해당 예외를 다른 예외로 대체하여 발생시켜야 한다. 또한 수많은 예외 정보를 처리하기 위하여 몇 개의 그룹으로 분류해 처리하는 경우도 있을 것이다. 이러한 다양한 예외 처리를 위한 시나리오가 있다면 예외 애플리케이션 블록을 이용해서 손쉽게 구현할 수 있다. 예외 처리에는 다음과 같이 세 가지의 핸들러가 있다.

 

* 포장 핸들러(Wrap handler)
예외 핸들러를 다른 예외 핸들러가 감싸는 경우, 이 때 기존 핸들러는 내부 예외(Inner Exception)에 담긴다.

* 대체 핸들러(Replace handler)
예외 핸들러를 다른 핸들러로 대체하여 발생시키는 핸들러이다.

* 로깅 핸들러(Logging handler)
예외 정보를 기록하기 위한 핸들러이다.

 

세 가지 아키텍처 구조에서의 예외 처리 시나리오를 보면 <그림2>와 같다.


 

<그림2> 세 가지 구조에서의 예외 처리 시나리오

 

데이터 액세스 레이어에서 발생된 예외는 로그에 기록을 하고 포장 핸들러로 분류를 해서 상위 레이어로 올린다. 예를 들면 데이터 접근을 하는데 있어 복구 가능한 경우와 복구 불가능한 경우가 있을 것이다. 한 예로 레코드에 잠금이 걸린 경우라면 조금 있다가 다시 시도하면 잠금이 풀릴 수 있으므로 이를 복구 가능한 예외(Recoverable Exception)로 설정하여 기존의 예외는 내부 예외(Inner Exception)에 담아서 예외를 발생시키는 것이다.

비즈니스 레이어에서는 해당 예외를 받아서 사용자에게 모든 예외 정보를 보여줄 필요가 없으므로 현재 예외 정보를 로깅하고 다른 예외로 대체하여 사용자에게 넘겨 줄 수 있다. 이러한 일련의 과정을 예외 애플리케이션 블록을 이용하면 유연성 있게 구축할 수가 있다.

다음은 DB에 접속하는 데 있어 테이블명이 틀려서 예외가 발행하는 데 로깅 핸들러를 달아서 로깅 정보에 기록하도록 하는 예제이다.

static void DoDBAction()

{

        SqlConnection conn = null;

        conn = new SqlConnection

                ("Data Source=(local);Initial Catalog=Northwind;Integrated Security=True;");

        try

        {

                conn.Open();

                SqlCommand cmd = new SqlCommand("SELECT * FROM Customers11", conn);

                cmd.ExecuteReader();

        }

        catch (Exception e)

        {

                bool rethrow = ExceptionPolicy.HandleException(e, "DataLayer");

                if (rethrow) throw;

        }

        finally

        {

                if (conn != null) conn.Close();

        }

}

static void Main(string[] args)

{

        DoDBAction();

}

위 소스는 예외가 발생하면 DataLayer의 예외 정책을 수행하고 예외를 throw 하고 있다. 여기에서 throw e; 하지 않고 throw; 라고 한 이유는 throw e;를 하게 되면 스택 정보가 모두 초기화 되므로 과거 정보가 없어지기 때문이다.


<화면10> DataLayer 예외 정책에서 텍스트 파일의 로깅 핸들러를 설정한 화면
 
 

이번에는 로깅 정보를 텍스트 파일에 저장하도록 해 보았다. 아래는 예외가 발행하여 텍스트 파일에 기록된 일부분이다.

 

----------------------------------------

Timestamp: 2006-06-11 오전 9:16:16

Message: HandlingInstanceID: dc2798d1-4b5c-4bf3-93f5-168aa1b4760a

An exception of type 'System.Data.SqlClient.SqlException' occurred and was caught.

----------------------------------------------------------------------------------

06/11/2006 18:16:16

Type : System.Data.SqlClient.SqlException, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Message : Invalid object name 'Customers11'.

Source : .Net SqlClient Data Provider

Help link :

Errors : System.Data.SqlClient.SqlErrorCollection

Class : 16

LineNumber : 1

Number : 208

Procedure :

Server : (local)

State : 1

 

이번에는 대체 핸들러를 달아 보자.

<화면11> 대체 핸들러를 설정한 화면
 
 

static void Main(string[] args)

{

        try

        {

                DoDBAction();

        }

        catch (Exception e)

        {

                Console.WriteLine("ErrorType:{0}, ErrorMessage:{1}",e.GetType().ToString(), e.Message);

        }

}


<결과>

ErrorType:System.ApplicationException, ErrorMessage:에러

 

<화면11>을 보면 DataLayer에 예외가 발생하면 System.ApplicationException이 대신 발생하고 메시지도 “에러”라는 메시지가 나오도록 설정을 하였다. 그래서 결과를 보면 원본 예외는 가려지고 System.ApplicationException 형식의 예외가 리턴이 되었다.

 

로깅 애플리케이션 블락(Logging Application Block)

애플리케이션을 개발하다 보면 많은 로깅 정보를 기록할 필요성을 느낄 것이다. 로깅 정보를 기록함으로써 해당 애플리케이션이 어떤 문제가 있는지, 잘 운영되고 있는지 등등의 문제점들을 미리 점검할 수 있다. 로깅 정보를 기록하기 위한 공간으로는 이벤트 로그, 이메일, 데이터베이스, 메시지 큐, 텍스트 파일, WMI 이벤트 등등 많은 공간에 로깅 정보를 저장 할 수 있다.

로깅 애플리케이션 블락을 이용하면 이러한 정보를 저장하는데 있어 통일된 방법을 제공한다. 만약 로깅 정보를 이벤트 로그에 저장하다가 텍스트 파일에 저장하도록 변경하더라고 소스 코드의 수정 없이 설정 파일만 변경해 주면 바로 반영이 가능하다. 또한 필터 기능을 이용하여 원하는 정보만 걸러서 저장하는 기능도 제공한다.

아래는 로깅 애플리케이션 블락의 예제이다.

static void WriteLog(string msg)

{

        LogEntry log = new LogEntry();

        log.Message = msg;

        log.Categories.Add("General");

        log.Priority = 2;


        Logger.Write(log);

}


static int Calc(int a, int b)

{

        int c=0;


        try

        {

                c = a / b;

        }

        catch(Exception e)

        {

                WriteLog(e.Message);

        }

        return c;

}


static void Main(string[] args)

{

        Calc(4, 0);

}

위 예제는 4를 0으로 나누어서 Divide by zero 예외가 발생하여 그 정보를 기록하는 예제이다. 이 로깅 정보를 기록하는데 있어 이벤트 로그에 저장하도록 아래와 같이 설정을 하자.

<화면6> 이벤트 로그에 기록하기 위한 설정

 

기본적으로 처음에 로깅 애플리케이션 블락을 추가하면 디폴트로 이벤트 로그용 설정 정보가 셋팅이 된다. 카테고리 이름을 General로 하였고, 위의 소스에서도 General카테고리를 사용하였다. General 카테고리에는 Formatted EventLog TraceListener가 붙어서 이벤트 로그에 기록하는 것임을 알 수 있을 것이다. 이를 실행하여 이벤트 로그를 확인해 보면 아래와 같다.

<화면7> 이벤트 로그에 기록된 정보

 

이번에는 필터 기능을 테스트 해보자. 우선 순위에 대한 필터를 걸어서 우선 순위가 2 이상인 것만 로깅 되도록 해보자.

<화면8> 우선순위 필터 셋팅 화면
 

 

static void WriteLog(int p)

{

        LogEntry log = new LogEntry();

        log.Message = "필터 테스트";

        log.Categories.Add("General");

        log.Priority = p;


        Logger.Write(log);

}


static void Main(string[] args)

{

        WriteLog(1);

        WriteLog(2);

        WriteLog(3);

}

 

우선 순위가 각각 1,2,3인 로깅 정보를 기록하고 있으며 필터에서 2 이상인 것만 기록하라고 했으므로 우선순위가 1을 제외한 2와 3인 정보만 기록이 될 것이다.

<화면9> 우선순위 2와 3만 기록된 화면

 

<화면9>는 기존의 이벤트 로그 정보를 다 지우고, 실행한 화면이다. 정확히 2개의 정보만 기록되어 있는 것을 확인 할 수 있을 것이다.

 

보안 애플리케이션 블락(Security Application Block)
사용자 권한 검증이나 로그인한 동안에 인증 정보를 캐시 처리하기 위하여 보안 애플리케이션 블락을 사용할 수 있다. 엔터프라이즈 라이브러리 1.0 버전에서는 권한 검증, 역할 프로바이더가 별도로 있었지만 이는 닷넷 프레임웍 2.0이 나오면서 ASP.NET에서 그러한 기능을 지원하기 때문에 빠졌다. 따라서 권한 검증을 위해서라면 보안 애플리케이션 블락이 아닌 ASP.NET의 멥버쉽 프로바이더를 이용하면 된다.

 

ASP.NET의 멥버쉽 프로바이더는 두가지가 있는데, 하나는 SQL Server 기반의 프로바이더이고, 다른 하나는 Active Directory 기반의 프로바이더이다. 또한 커스텀 프로바이더라고 해서 개발자가 직접 만들어서 사용할 수도 있다.

 

이번 연재에서는 SQL Server 기반의 프로바이더를 사용할 것인데, 이를 사용하기 위해서는 ASP.NET SQL Server 등록 툴을 사용하면 쉽게 SQL Server용 데이터를 만들 수 있다. 이 프로그램은 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 폴더에 가면 aspnet_regsql.exe이라는 파일로 실행할 수 있다. 아래와 같이 실행을 하면 로컬 DB에 멤버쉽과 역할 매니저 정보를 설치할 수 있다.

 

aspnet_regsql.exe -E -S localhost -A mr

 

이 명령어를 실행하면 로컬 데이터베이스에 aspnetdb라는 데이터베이스가 생긴다. 이를 사용하기 위해서는 애플리케이션 설정 파일에 아래와 같은 설정 정보를 추가해 줘야만 한다. 이 정보는 엔터프라이즈 라이브러리에서 설정하는 정보가 아니므로 엔터프라이즈 라이브러리 설정 툴을 이용하여 셋팅 할 수 없는 부분이다.

 

<system.web>

        <membership defaultProvider="SqlProvider">

                <providers>

                        <clear />

                        <add

                                name="SqlProvider"

                                type="System.Web.Security.SqlMembershipProvider"

                                connectionStringName="SqlServices"

                                enablePasswordRetrieval="false"

                                enablePasswordReset="true"

                                requiresQuestionAndAnswer="false"

                                applicationName="/"

                                requiresUniqueEmail="false"

                                minRequiredPasswordLength="1"

                                minRequiredNonalphanumericCharacters="0"

                                passwordFormat="Hashed"

                        />

                </providers>

        </membership>

</system.web>

 

이제 이를 이용하여 사용자 인증을 해보자.

public static bool Authenticate1(string id, string pw)

{

        bool authenticated = false;

        authenticated = Membership.ValidateUser(id, pw);

        return authenticated;

}


static void Main(string[] args)

{

        // 사용자 생성

        Membership.CreateUser("han", "123");

        Membership.CreateUser("kim", "abc");


        Console.WriteLine("[유저 생성] ID:han, PW:123");

        Console.WriteLine("[유저 생성] ID:kim, PW:abc");


        // 로그인 테스트

        bool authenticated;

        authenticated = Authenticate1("han", "345");

        Console.WriteLine("[유저 로그인]: ID:han, PW:345 결과:{0}", authenticated.ToString());


        authenticated = Authenticate1("han", "123");

        Console.WriteLine("[유저 로그인]: ID:han, PW:123 결과:{0}", authenticated.ToString());

}


<결과>

[유저 생성] ID:han, PW:123

[유저 생성] ID:kim, PW:abc

[유저 로그인]: ID:han, PW:345 결과:False

[유저 로그인]: ID:han, PW:123 결과:True

 

보안 애플리케이션 블락에서는 이렇게 인증이 끝나면 역할 방식의 권한 검증을 할 수 있는 기능을 제공한다. 예를 들면 3가지의 작업이 있다고 하자. 사용자 작업, 관리 작업, 모니터링 작업. 이때 사용자도 3가지 부류로 나눈다. 관리자, 유저, 개발자.
이중에 사용자 작업은 유저만 할 수 있고, 관리 작업은 관리자, 모니터링 작업은 개발자와 관리자 두 부류만 할 수 있다고 가정하자. 그러면 <화면13>과 같이 설정할 수 있다.

<화면13> 권한 검증을 위하여 역할 지정

 

* 사용자 작업(Use) : 사용자(User)
* 관리 작업(Manage): 관리자(Manager)
* 모니터링 작업(Monitor): 관리자(Manager), 개발자(Developer)

 

이렇게 설정 하였다면 이를 사용하는 예제를 보자.

 

// 인증과 함께 롤 배정

public static bool Authenticate2(string id, string pw)

{

        bool authenticated = false;

        authenticated = Membership.ValidateUser(id, pw);

        if (!authenticated) return false;


        IIdentity identity;

        identity = new GenericIdentity(id, Membership.Provider.Name);


        string[] roles = Roles.GetRolesForUser(identity.Name);

        IPrincipal principal = new GenericPrincipal(identity, roles);


        Thread.CurrentPrincipal = principal;

        return authenticated;

}


public static bool Authorized(string rule)

{

        bool authorized = false;


        IAuthorizationProvider ruleProvider;

        ruleProvider = AuthorizationFactory.GetAuthorizationProvider();

        authorized = ruleProvider.Authorize(Thread.CurrentPrincipal, rule);


        return authorized;

}


public static void DoMonitorAction()

{

        if (!Authorized("Monitor"))

        {

                Console.WriteLine("권한 오류");

        }

        else

        {

                Console.WriteLine("적합한 권한을 가지고 있습니다.");

        }

}


static void Main(string[] args)

{

        // 롤 생성

        Roles.CreateRole("User");

        Roles.CreateRole("Admin");

        Roles.CreateRole("Developer");

           

        Roles.AddUserToRole("han", "Admin");

        Roles.AddUserToRole("kim", "User");


        //롤 테스트

        Authenticate2("kim", "abc");

        Console.Write("kim 모니터링 작업 시도: ");

        DoMonitorAction();


        Authenticate2("han", "123");

        Console.Write("han 모니터링 작업 시도: ");

        DoMonitorAction();

}


<결과>

kim 모니터링 작업 시도: 권한 오류

han 모니터링 작업 시도: 적합한 권한을 가지고 있습니다.

 

먼저 3가지 역할을 할당을 하고 han 이라는 사용자에게는 관리자의 역할을 부여하고 kim이라는 사용자에게는 유저라는 역할을 부여 하였다. 사용자 인증을 하면서 현재 쓰레드 정보에 자신의 역할 정보를 기록하고 DoMonitorAction()이라는 메소드를 호출할 때, 이 메소드는 모니터 작업을 할 수 있는 사용자가 실행하는지 권한 검증을 수행한다. 따라서 kim이라는 유저가 모니터링 작업을 하려고 하면 권한 오류 에러를 발생 시키고 han이라는 사용자가 모니터링 작업을 하려하면 작업을 수행하게 한다.

 

인스트러멘테이션(Instrumentation)

성능 카운터, 이벤트 로그, WMI 이벤트에 정보를 기록하기 위해서는 인스트러멘테이션(instrumentation) 기능을 사용하면 된다. 이 기능을 사용가능 하게 하려면 설정 파일에 아래와 같은 설정을 셋팅해 주어야만 한다.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section name="instrumentationConfiguration"

type="Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.Configuration.InstrumentationConfigurationSection,

    Microsoft.Practices.EnterpriseLibrary.Common, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" />

   </configSections>

 <instrumentationConfiguration performanceCountersEnabled="true"

  eventLoggingEnabled="true" wmiEnabled="true" />

</configuration>

 

위의 설정을 보면 성능 카운터, 이벤트 로그, WMI 이번트 기록을 모두 사용 가능하도록 셋팅을 하고 있다. 이렇게 셋팅을 한 후에 보고자 하는 기록을 보면 된다. 이번 예제에서는 데이터 액세스 애플리케이션 블락의 커맨드 실행 횟수를 카운트 해보자.

 

 

static void DataAccess()

{

        Database db = null;

        db = DatabaseFactory.CreateDatabase("NWConn");


        int results = (int)db.ExecuteScalar(CommandType.Text,

                "SELECT COUNT(*) From Customers");

        Console.WriteLine("Count:{0}", results);

}


static void Main(string[] args)

{

        for (int i = 0; i < 50; i++)

        {

                DataAccess();

        }

}


<결과>

Count:91

Count:91

Count:91

Count:91

...

 

위 프로그램은 고객 테이블의 개수를 읽어 오는 DataAccess() 메소드를 50번 연속으로 호출하고 있다. 이를 성능 카운터에서 보기 위해서는 아래와 같이 추가를 하자.

<화면14> 데이터 액세스 애플리케이션 블락에서 커맨드 실행 횟수 측정 셋팅

 

이렇게 성능 카운터를 추가하고 위 프로그램을 실행시켜 보자 그러면 아래와 같은 결과를 볼 수 있을 것이다.

<화면15> 성능 카운터로 측정한 결과

 

50번 연속 실행하면서 성능 카운터의 수치가 올라가는 것을 볼 수 있을 것이다.


 

마치면서

두 달에 걸쳐 엔터프라이즈 라이브러리 2.0에 대해 알아보았다. 엔터프라이즈 라이브러리는 프로젝트가 성공할 수 있도록 기초 공사를 충실히 해주는 라이브러리이다. 만약 이러한 기반이 없이 프로젝트를 수행했다가 갑자기 애플리케이션이 다운 된다던지, CPU 사용량이 100을 쳐서 하드웨어가 감당할 수 없을 만큼 느려진다든지 할 때, 이러한 기초 공사를 잘 해 놓으면 그 원인을 쉽게 찾을 수 있다. 하지만 이러한 기초 공사가 없다면 그러한 문제가 일어났을 때, 해결책을 찾기 위해서 많은 시간을 소비해야 할 것이다. 결국 그 원인을 찾아낸다 해도 이미 많은 시간이 흘렀기 때문에 그 피해는 되돌릴 수 없는 것이다. 성공적인 프로젝트를 위한 필수조건이 바로 튼튼한 기초 공사에 있다는 것은 여러 번 강조해도 지나치지 않다.