본문 바로가기

IT 살이/04. 기술 - 프로그래밍

애플리케이션 도메인과 속성들(베이스 디렉토리)

애플리케이션 도메인(AppDomain)과 환경 속성들

이 포스트에서는 애플리케이션 도메인과 그와 관련된 도메인 속성들에 대해서 알아본다. 이 포스트에서 중요한 개념은 애플리케이션의 베이스 디렉토리와 환경 설정 파일 .config이다. 애플리케이션의 베이스 디렉토리의 개념을 이해하는 것은 특히 스마트클라이언트 애플리케이션에서의 어셈블리 바인딩과 배포(특히 NTD배포)를 이해하는데 있어서 중요한 개념이다.

애플리케이션 도메인은 AppDomain이라는 클래스로 구현되어 있다. AppDomain에는 여러가지 환경 정보들을 가지고 있고, 이런 정보들은 퓨전을 제어하는 중요한 정보들이다. AppDomain의 환경 속성값들은 퓨전이 어셈블리를 검색할 때 이용하게 되는 중요한 정보들이다. 이런 환경 속성값들은 애플리케이션의 설정 파일 .config에 설정된다. 애플리케이션이 실행이 되면 CLR은 우선 .config 파일을 읽어들여서 이런 속성값들을 채우게 되다.

우선 애플리케이션이 실행되는 논리적인 공간이라고 한 AppDomain에 대해서 개념적으로 이해해보자. 코드의 실행과 리소스의 소유 범위를 결정하는 모델은 프로그래밍 모델, 기술에 따라서 항상 있어왔다. OS에서는 프로세스(process)가 애플리케이션의 경계가 되었고, IIS와 ASP에서는 가상 디렉토리를 기준으로 애플리케이션이 구분되었고 그리고 .NET 실행환경에서는 실행공간을 나누기 위해서 애플리케이션 도메인 개념을 사용하고 있는 것이다. 애플리케이션 도메인은 하나의 프로세스에 여러 개 있을 수 있다.
1029095727

Process 공간과 AppDomain 공간
좀더 상세한 그림은 CLR via C#- Jeffrey Richter( Chapter 21: CLR Hosting and AppDomains를 참조한다.)

.NET 애플리케이션이 시작되면, CLR은 기본적으로 하나의 애플리케이션 도메인을 생성하고 그 안에서 애플리케이션을 실행시킨다. IE 기반의 스마트클라이언트 애플리케이션에서도 브라우저가 최초의 어셈블리를 호출할 때 클라이언트에 애플리케이션이 하나 생성된다.

또한 프로그램적으로도 애플리케이션 도메인을 생성하고 그 속에서 또 다른 애플리케이션을 실행시킬 수도 있다. 그래서 그림처럼 하나의 OS 프로세스에 복수개의 애플리케이션 도메인이 존재할 수 있다.

두 애플리케이션이 동일한 어셈블리를 참조하고 있다면 어떻게 될까? 애플리케이션은 각자의 애플리케이션 도메인으로 어셈블리를 로딩하고 객체도 각각의 도메인 안에서 따로 생성한다. 그래서 그림처럼 각 애플리케이션 도메인마다 동일한 타입의 객체들이 독립적으로 존재하게 된다. 애플리케이션 도메인간에는 객체의 인스턴스 멤버뿐만 아니라 정적 멤버도 공유가 되지 않는다. 독립된 실행 공간이다.

독립된 공간이긴 하지만 두 공간 사이의 객체를 서로 접근 못하는 것은 아니다. 그러나 이것은 이 책의 범위를 벗어나는 주제이다. 뒤의 레퍼런스에서 소개하는 서적이나 아니면 다른 좀더 전문적인 문서를 참고하기 바란다. 여기서 말하고 싶은 요점은 하나의 .NET 애플리케이션은 애플리케이션 도메인이라는 하나의 독립된 공간에서 실행된다는 것이다.

앞에서 말한 애플리케이션 도메인의 환경 속성은 System.AppDomainSetup이라는 타입의 데이터 구조에 저장되어 있다. 이런 환경 설정값들은 AppDomain의 SetupInformation이라는 속성으로 사용자들에게 노출되고 있다.

AppDomain의 SetupInformation 속성을 이용하여 이런 환경 속성에 접근할 수도 있지만, 사용자는 GetData, SetData 메소드를 사용할 수도 있다. 환경 속성들은 이미 정해진 문자열 속성 이름을 가지고 있는데, 이 메소드들은 그 속성 문자열을 인자로 사용한다. 즉 다음과 같은 코드는 동일한 값을 반환한다.

string appbase = AppDomain.CurrentDomain.SetInformation.ApplicationBase;
string appbase = AppDomain.CurrentDomain.GetData("APPBASE");

이런 환경 속성들을 숙지하고 적절히 설정함으로써 애플리케이션의 개발, 배포, 운영상에서 발행할 수 있는 이슈들을 매끄럽게 해결할 수 있게 된다. 다음 표는 환경 속성을 나타내는 AppDomainSetup의 속성들과 그에 대응하는 Get/SetData의 속성 문자열을 몇 가지 보여주고 있다.
1364111840

애플리케이션 도메인 속성값 접근법 #1

이런 환경 속성중에서 어떤 것은 AppDomain에서도 직접 접근할 수 있도록 하고 있다.
1217830451

애플리케이션 도메인 속성값 접근법 #2



환경 설정값에 접근할 수 있도록 사용자에게 노출된 방법들이 이와 같으니 어떤 경우는 동일한 값에 접근하기 위하여 몇 가지 방법을 사용할 수 있다.
using System;
static void Main()
{
// 현재 AppDomain 인스턴스를 구한다.
  AppDomain current = AppDomain.CurrentDomain;
  //APPBASE 디렉토리를 구하는 3가지 방법
  string base1 = current.BaseDirectory;
  string base2 = current.SetupInformation.ApplicationBase;
  string base3 = (string)current.GetData(“APPBASE”);
  Console.WriteLine("base1:" + base1);
  Console.WriteLine("base2:" + base2);
  Console.WriteLine("base3:" + base3);

이 프로그램을 실행시키면, APPBASE 값은 프로그램이 실행되는 위치의 경로로 설정된다.
1252845535

애플리케이션 디렉토리 출력

그러나 이런 설정값들은 AppDomain 생성 후에는 변경이 불가하다.

AppDomain.CurrentDomain.ApplicationName = "MyApplication";

.NET 애플리케이션이 실행되면 이미 기본 AppDomain이 생성되고 그곳에서 애플리케이션이 실행된다. 따라서 이와 같은 코드는 적용되지 않는다. 따라서 이런 설정들이 나타내는 개념들을 이용하기위해서는 기본 AppDomain에서 새로운 AppDomain을 생성하는 프로그램을 작성할 필요가 있다. AppDomain을 이용한 프로그램이 그다지 어려운 것은 아니지만 이것은 이 책에서 벗어나는 주제이므로 이 책에서는 다루지 않고 있다. 관심있는 독자는 좀더 .NET 프로그래밍 위주의 서적을 참고하기 바란다. 다만 여기서는 애플리케이션 도메인의 속성들이 무엇을 말하는지 코드없이 개념 중심으로 알아본다.

애플리케이션 베이스 디렉토리 결정

애플리케이션 기본 디렉토리 Appbase값은 실제적인 파일 시스템상의 경로로서 참조되는 다른 어셈블리를 검색할 때 기준이 되는 디렉토리값이다. 애플리케이션 도메인이 애플리케이션이 실행되는 논리적인 공간이라면 그것을 실제 물리적인 경로와 연결시켜주는 것이 애플리케이션 기본 디렉토리 값이라고 볼 수 있다.
1164115792

애플리케이션 도메인, 베이스 디렉토리

AppDomain의 Appbase값은 설정 파일이 있는 경우는 그 파일이 로딩된 디렉토리값이 된다. 그러나 설정 파일이 없다면 .exe 파일이 로딩된 위치가 된다. 예를 들어 "c:\App" 또는 "http://localhost/MyApp/"가 Appbase 값이 될 수 있다. 그리고 웹 페이지 aspx에서 <object>태그에 의해 호출되는 스마트클라이언트 애플리케이션의 Appbase값은 배포 서버의 주소가 된다. 비록 <object>에 의한 어셈블리가 http://localhost/MyApp/아래에 있다 하더라도 Appbase값은 "http://localhost/"이 된다.

스마트클라이언트 애플리케이션의 경우에서처럼 Appbase값은 반드시 애플리케이션이 실행되는 머신상의 경로를 나타내는 것은 아니다. 스마트클라이언트 애플리케이션은 클라이언트측에서 실행되지만 Appbase는 http://~로 시작하는 원격 서버의 경로를 가리킨다.

달봉이는 Appbase값을 처리하는 곳에서 .NET 프레임워크가 데이스크탑 애플리케이션과 웹 애플리케이션을 위한 통합 프레임워크임을 재삼 느끼곤한다. 개발자는 Appbase가 로컬 머신의 경로이든 다른 네트워크상의 머신의 경로이든 상관없이 사용할 수 있다는 것이다. 예를 들어 다음과 같은 코드가 있을 수 있다.

Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "MyApp.exe");

개발자는 이런 단일 형태의 코드만 작성하면 된다. 어셈블리 로더가 애플리케이션의 Appbase값을 통해 어셈블리 검색의 시작 위치를 확인하고 그 경로 표시 형태에 따라 후속작업을 하게 된다.

AppDomain이 생성될 때 동적 디렉토리(Dynamic Directory)라 불리는 값이 설정될 수 있다.  .NET에서는 어셈블리를 코드상에서 프로그램적으로 생성할 수 있는데, 이렇게 동적으로 생성되는 어셈블리가 저장될 디렉토리가 동적 디렉토리의 값으로 설정된다. CLR은 어셈블리를 검색할 때 .config의 <probing>요소의 privateBin에 설정된 디렉토리를 검색하기전에 동적 디렉토리를 먼저 확인한다. 가장 자주 코드를 동적으로 생성하는 녀석이 ASP.NET이다. ASP.NET이 설치되어 있는 컴퓨터의 동적 디렉토리는 아래 경로의 하위에 있다. 이곳을 보면 웹 애플리케이션별로 동적으로 생성된 어셈블리가 저장되어 있다.

%windir%\Microsoft.NET\Framework\<.NETVersion#>\Temporary ASP.NET Files\

ASP.NET의 경우는 웹 애플케이션이 만드는 모든 어셈블리를 이곳에 저장한다. CLR은 어셈블리 검색 과정에서 항상 이곳을 먼저 들러 보기 때문에 ASP.NET에서는 Appbase를 검색하지 않도록 BINPATH_PROBE_ONLY 속성을 설정해놓고 있다. ASP.NET에서는 APPBASE 디렉토리 검색을 막고 대신에 BINPATH_PROBE_ONLY 값으로 "bin"을 설정해 놓고 있다. 때문에 ASP.NET에서는 필요한 어셈블리가 있다면 APPBASE가 아니라 bin 폴더에 넣어두어야 한다.

다음에 나오는 FORCE_CACHE_INSTALL, SHADOW_COPY_DIRS, CACHE_BASE는 섀도우 복사(Shadow copy)와 관련된 속성들이다. 섀도우 복사는 서버측 애플리케이션과 배포와 관련된 일반적인 문제와 관련있다. 서버측 환경(IIS, COM+)에서 DLL이 한번 로딩되면 그 어셈블리에 다른 수정을 가하지 못하다록 읽기 락(read lock)이 걸린다. 따라서 DLL이 IIS, COM+환경하에서 로딩되고 나면 새로운 버전으로 그것을 덮어쓸 수가 없게 된다. 결국 새로운 버전을 배포하려면 애플리케이션의 실행을 중지시키고 해당 DLL의 락을 해제한 후에나 가능하게 된다. 섀도우 복사는 하나의 AppDomain에 대하여 설정할 수 있는 설정이다.

현장의 프로젝트에서도 이것과 관련한 요구사항이 종종 있다. "배포를 하기위해서 꼭 서버를 내려야 하느냐? .NET에서는 동일한 어셈블리가 버전만 다르면 동시에 존재할 수 있다고도 하는데, 서버를 내리지 않고도 배포를 할 수 있는 방법이 있지 않겠느냐? "는 것이다.

섀도우 카피는 이런 문제를 해결하기위해 사용될 수 있는 개념이다. CLR이 섀도우 복사를 이용하여 어셈블리를 로딩할때는 일단 임시 디렉토리로 어셈블리를 복사하고 나서 원본 대신에 그 복사본을 로딩하는 것이다. 따라서 복사본이 로딩되어 있는 동안에도 원래의 디렉토리로 배포가 가능하다.

섀도우 복사를 활성화하기 위해서는 3가지 설정이 필요하다.

1.       섀도우 복사를 활성화할지에 대한 여부
2.       어떤 디렉토리에 있는 어셈블리를 섀도우 복사할 것이지?
3.       어떤 디렉토리로 복사할 것인지?

따라서 섀도우 복사를 사용하려면 섀도우 복사의 대상이 디렉토리 즉 원본이 들어 있는 디렉토리(SHADOW_COPY_DIRS)와 복사본이 임시로 저장될 디렉토리(CACHE_BASE)에 대한 설정이 있어야 한다.

새도우 복사의 활성화 방법

- AppDomainSetup.ShadowCopyFiles 속성을 true로 설명하면 섀도우 복사가 활성화된다.

어떤 디렉토리에 있는 어셈블리를 섀도우 복사할 것인지?

- AppDomainSetup.ShadowCopyDirectories 속성은 섀도우 복사될 디렉토리를 나타낸다. 세미콜론(;)로 분리된 절대경로의 디렉토리 목록을 값으로 취한다. 이제 이 디렉토리에 있는 어셈블리가 로딩되면 섀도우 복사될 것이다. 만약 ShadowCopyDirectories값이 null이면 로딩되는 모든 어셈블리들이 섀도우 복사된다.

어디로 복사할 것인지.

- AppDomainSetup.CachePath + AppDomainSetup.ApplicationName 이 두 속성값의 연결문자열이 어셈블리가 어디로 섀도우 복사될 것인지를 결정한다. 만약 CachePath와 ApplicationName 속성 둘다 설정되었다면 CachePath\ApplicationName로 복사될 것이다. 그렇지 않다면 다운로드 캐시(%사용자계정명%\local settings\application data\assembly)로 복사된다.

CachePath+ApplicationName을 설정했다면 이곳의 어셈블리를 삭제하는 일은 직접 해야 한다. 그러나 다운로드 캐시에 복사된 어셈블리는 퓨전이 자동적으로 관리해준다. 퓨전과 다운로드 캐시는 조금 뒤에 나온다.

.NET 애플리케이션이 실행되면서 생성되는 기본 애플리케이션 도메인에서는 AppDomainSetup을 변경할 수 없다. 그러나 AppDomain의 API를 사용하면 속성을 변경할 수 있다. AppDomain의 관련 API는 다음과 같다: SetCachePath, SetShadowCopyFiles, SetShadowCopyPath. ApplicationName은 기본 애플리케이션 도메인의 애플리케이션 이름으로 설정된다.

AppDomain.CurrentDomain.SetShadowCopyFiles();
//AppDomain.CurrentDomain.SetCachePath(@"C:\cache");
AppDomain.CurrentDomain.SetShadowCopyPath(@"D:\SC_SourceCode\Hello_Solution\30_Test\TestWin\bin\Debug\dir");


애플리케이션 타입별 설정 파일( .config)

환경 설정 파일 .config이 없는 애플리케이션도 있을 수 있지만, 규모가 있는 대부분의 애플리케이션은 .config 파일을 이용해서 환경 설정을 하게 된다. CLR은 애플리케이션을 시작시키면서 환경 파일이 있는지를 확인하고, 설정 파일이 있는 애플리케이션인 경우는 설정 파일을 읽어들여 AppDomain의 환경 속성값을 초기화한다고 했다. CLR은 이 설정 파일을 어디서 찾을까? 애플리케이션 타입별로 설정 파일 위치가 다르다.

.exe 애플리케이션의 경우는 .exe 파일이 있는 위치에 .config 파일이 있어야 한다. .exe 파일을 로컬 PC 하드 드라이브에 있던 아니면 웹 서버에서 다운받던 상관없다. 그리고 그 파일명은 반드시 <애플리케이션명>.exe.config와 같은 형식으로 되어 있어야 한다.

ASP.NET 애플리케이션과 XML 웹 서비스 애플리케이션의 경우는 웹 애플리케이션의 가상 루트 디렉토리에 있어야 하며 이름은 web.config로 되어 있어야 한다. web.config 파일은 루트 디렉토리의 하위 디렉토리에도 있을 수 있는데, 이런 경우는 상위 디렉토리의 설정값을 상속받아서 상위 설정값을 오버라이딩할 수도 있고 또 설정을 추가할 수도 있다.

브라우저 기반의 스마트클라이언트 애플리케이션은 다음처럼  HTML의 <link> 태그를 사용한다.

rel 어트리뷰트는 "Configuration"으로 설정하고 href어트리뷰트에 .config 파일에 대한 URL을 설정하면 된다. 설정 파일의 이름과 확장자 조차도 임의로 작성할 수 있다. 설정 파일은 최소한 다음과 같은 모양을 하고 있어야 한다.

<configuration>
</configuration>

설정 파일에는 .NET에서 지정한 여러 XML 섹션이 있을 수 있다. 예를 들어서 흔히 사용되는 섹션중의 하나가 <appSettings>이다. 쉽게 변할 수 있는 값들을 여기에 설정해 놓고 코드의 변경없이 자주 값을 수정할 수 있도록 한다. 데이터베이스 연결 문자열을 이곳에 설정해 놓는 것이 대표적인 예이다.

<configuration>
  <appSettings>
       <add key="connectionstring"
                  value="server=203…..uid=…pwd=…database=…"/>
            <add key="number" value="1"/>
  </appSettings>
</configuration>

설정 파일의 각 섹션은 그 섹션의 값을 읽고 해석해낼 수 있는 섹션 리더(section reader)라는 것이 있다. 이런 섹션 리더는 애플리케이션의 .config 파일에 정의될 수도 있고, 머신 차원의 machine.config에 정의될 수 있다. <appSettings>요소의 섹션 리더는 machine.config에 다음과 같이 정의되어 있다.

<configuration>
  <configSections>
       …
       <section
                name="appSettings"
                type="System.Configuration.NameValueFileSectionHandler,
                System,
                Version=1.0.5000.0,
                Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
            …

어셈블리 System.dll에 있는 NameValueFileSectionHandler 클래스는 <appSettings>요소내부의 섹션을 읽고 해석하여 NameValueCollection 객체를 반환한다는 내용을 정의하고 있다. 섹션 리더는 인터페이스 IConfiguraionSectionHandler를 구현해야 하고 <configSections>요소에 추가하면 등록이 된다. 

NameValueFileSectionHandler 클래스는 .NET 프레임워크 내부에서만 사용되고 대신에 사용자에게는 ConfigurationSettings, AppSettingsReader 클래스를 제공하고 있다.

<AppSettings> 자주 사용되는 요소이므로 여기서 ConfigurationSettings, AppSettingsReader 클래스들의 사용법을 간단히 소개한다.

using System.Configuration;
using System.Collections.Specialized;

NameValueCollection settings =
(NameValueCollection)ConfigurationSettings.GetConfig("appSettings");
...

ConfigurationSettings 클래스는 "appSettings"에 해당하는 섹션 핸들러를 구하고 다시 해당 섹션 핸들러는 현재 애플리케이션의 .config 파일에서 <appSettings>섹션에 있는 데이터를 파싱해서 NameValueCollection 객체를 만들어서 반환한다.

<appSettings> 섹션은 자주 사용된다는 이유로 ConfigurationSettings 클래스는 AppSettings라는 속성을 만들어 놓고 있다. 이것은 반환값이 NameValueCollection 타입이어서 타입 변환이 필요없다. 적당한 key 값을 인덱서 인자로 사용하면 반환된 NameValueCollection에서 필요한 값을 얻을 수 있다.

NameValueCollection settings = ConfigurationSettings.AppSettings;
string connectionstring = settings["connectionstring"];

ConfigurationSettins 클래스대신에 AppSettingsReader 클래스를 사용할 수도 있다.

AppSettingsReader reader = new AppSettingsReader();
int number = (int)reader.GetValue("number", typeof(int));

환경 설정 파일은 어셈블리 바인딩을 제어하는데도 중요하게 사용된다.

참조문서

Essential .NET Volume 1 - Don Box with Chris Sells
CLR via C# - Jeffrey Richter