닷넷은 아직도 ‘정말 큰 규모의 응용 프로그램의 동작은 어려운’ 환경으로 인식되고 있다. 하지만 실제로는 그렇지 않다. 닷넷은 거대한 기업에서도 힘을 발휘할 수 있는 환경이다.

이 연재에서는 닷넷 플랫폼에서 기업형 환경을 쉽게 구축할 수 있도록 해주는 구조와 그 구조에 맞는 설계를 도와주는 기업형 응용 프로그램 패턴에 대해 기업형 구조와 설계 패턴을 중심으로 기업형 아키텍처와 엔터프라이즈 디자인 패턴에 대해 설명하는 것을 목적으로 한다.

IT 기술은 다른 어떤 공학 기술과 비교할 수 없을 정도로 빠르게 진화한다. 객체지향 기술이 끝인가 싶더니, 컴포넌트 기반 기술, MDA(Model Driven Architecture), SOA(Service Oriented Architecture) 등 수없이 많은 새로운 것이 등장하고 또 없어지기를 반복한다. 현재로서는 실무에 가장 잘 적용되는 기술로 CBD(Component Based Development)가 각광받고 있고, 실무에서도 CBD를 개발에 적용하려 하지만, 경험이 부족한 많은 개발자들은 이런 CBD를 어떻게 개발에 적용해야 할지 막막한 경우가 대부분이다.

제대로 된 CBD를 위해서는 기반 아키텍처를 구성하는 것이 제일 중요하다. 기업형 응용 프로그램에서 CBD를 제대로 하기 위한 아키텍처를 설계하는 방법이 제일 좋다. 하지만, 아키텍처 설계에는 많은 비용과 시간이 소모되므로 신뢰할 만한 기업에서 미리 설계해 놓은 아키텍처를 기반으로 새 아키텍처를 구성하거나 또는 기 설계된 아키텍처를 기반으로 응용 프로그램을 구성하는 것이 일반적이다.

‘어떤 컴포넌트를 만들어야 하는가?’에 대한 올바른 가이드라인을 제시해주는 아키텍처를 찾아 제대로 적용하는 것이 중요하다 할 수 있는데, MS에서 제시하는 엔터프라이즈 솔루션 아키텍처(Enterprise Solution Architecture)는 닷넷 기반 기업형 응용 프로그램을 작성할 때 기반이 될만한 가장 좋은 구조라 할 수 있다.

자바의 아성에 대한 도전
지난 98년, 썬 마이크로시스템즈는 당시 각광받던 자바의 여러 기술들을 확장해 기업 환경에서 신뢰성 있게 동작할 수 있는 기업형 분산 트랜잭션 지원 시스템인 EJB를 발표했다.

EJB는 거대한 기업형 응용 프로그램에서 반드시 필요한 기술들인 분산 트랜잭션, 이기종 데이터베이스 분산질의, 원격 프로시저 호출 등의 구현을 지원하는 기업 환경이었고, 자바가 주로 동작하는 운영체제인 유닉스가 탁월한 안정성을 보장하는 신뢰성 있는 운영체제였다. 이런 여러 장점들과 경쟁상대로 꼽을만한 기업형 응용 프로그램 개발 환경이 당시로서는 드물었으므로 EJB는 IT 시장에서 독보적이기까지 한 존재로 성장했다.

MS의 ASP는 서버 사이드 스크립트 역사상 가장 성공한 개발 환경이다. 1998년 윈도우 NT의 옵션팩에 포함되어 배포된 ASP 2.0은 세계적으로 3000만 명의 프로그래머들을 양산하며 웹 프로그래밍 환경에서 가장 많이 사용되는 서버 사이드 프로그래밍 환경이 되었다.

하지만 ASP와 MTS, COM 등 3가지 프로그래밍 환경이 주를 이루던 MS의 개발 환경은 기업 시장에서 힘을 발휘하기에는 자바와 비교하여 역부족이었다. 간단하지만 재사용성이나 가용성적인 측면에서 객체지향 언어를 기본으로 하고 컴포넌트 기술을 기반으로 하는 자바와 비교할 때, 그리고 MS의 개발 환경이 동작하는 윈도우 NT가 유닉스와 비교할 때 안정성과 신뢰성 측면에서 많은 불안 요소와 개발 지연성을 가지고 있었기 때문이다.

MS는 이에 대응하여 닷넷이라는 새로운 프로그래밍 환경을 발표했다. IT 시장의 흐름에 발 빠르게 대응해 기업 시장에서 자바의 아성에 도전하려는 듯 재사용성 및 가용성, 교체 가능성 등 자바의 장점들을 모두 가지려는 시도였다. 더 쉬운 프로그래밍과 변화하는 비즈니스 환경에 좀 더 손쉽게 대응할 수 있는 컴포넌트를 가진 닷넷은 패키지 응용 프로그램이나 소규모의 웹 응용 프로그램 등에 사용되던 이전 환경에서 벗어나 기업에서 MS 로고가 힘을 발휘할 수 있는 여건을 마련했다.

이는 MS가 2000년에 발표한 윈도우 2000 서버 제품군의 탁월한 성능이 큰 힘을 발휘했다. 닷넷 환경은 자바에 대응할 수 있는 대규모 기업형 응용 프로그래밍에 적합하게 설계되었고, 신뢰성 있게 동작하며, 쉬운 프로그래밍 인터페이스와 발전된 프레임워크를 가지고 있는 기업형 응용 프로그램 제작/배포 환경이다.

닷넷은 거의 모든 코드를 관리되는 환경에서 작성할 수 있기에 운영체제의 API에 직접 액세스하는 등의 위험요소를 줄일 수 있고, 컴포넌트 기반 프로그래밍 환경으로 구성되어 높은 재사용성과 교체 가능성 및 유지 보수성을 기대할 수 있게 구성되었다. 항상 하는 말이지만 닷넷 환경은 MS가 실로 오랜만에 만들어낸 ‘작품’이다.

하지만 환경이 제아무리 훌륭하다 하더라도, 그것에 걸 맞는 훌륭한 응용 프로그램의 작성은 힘들기 마련이다. 기반 아키텍처를 구성하고, 그 기반으로 프로그램을 작성한다는 것은 몇 권 분량의 책이나 몇 주 분량의 기사로 설명할 수 있는 것이 아니다.

또한 CBD라는 것이 양날의 검이 될 수 있는 것이기에 실제 프로그램을 작성하는 시간이나 비용보다 기반 프레임워크가 아키텍처를 구성하는 데 걸리는 시간이 훨씬 많이 소모될 수 있다. 이런 문제에서 본다면 프로그래밍 환경에서 중요한 것은 기반 구조가 아니라 생산성이 될 수 있다.

닷넷은 그 어떠한 프로그래밍 환경보다 생산성이 뛰어나지만 그것만으로는 한계가 있다. 한정된 인력이 거대한 규모의 프로젝트를 진행할 때, 또는 한정된 인력으로 구성된 회사가 여러 프로젝트를 동시에 진행할 때가 대표적인 경우다. 제대로 설계된 아키텍처는 프로그래밍의 길잡이 노릇을 해 줄 뿐만 아니라, 프로젝트를 성공적으로 이끌 수 있는 이정표 역할을 해 준다.

컴포넌트는 '어떻게' 구성돼야 하는가?
시작한 프로젝트가 CBD라고는 하는데, 이 CBD라는 것이 애매모호한 경우가 많다. 특히 ASP 개발자라면 페이지에다 로직을 프로그래밍하고 실행해보고 테스트해보고 성공하면 배포하고 돌아가지 않는다면 디버깅하고 하는데 익숙하다. 그리고 작성한 페이지의 데이터베이스 액세스 로직 등을 비주얼 베이직 등을 이용해 COM 컴포넌트로 컴파일해서 ASP 페이지에서 CreateObject 메쏘드를 호출해서 사용하는 것 정도도 익숙하다.

하지만 이 CBD라는 것이 어떤 컴포넌트를 어떻게 만들어서 어떻게 조립해서 사용해야 하는 것인지에 대한 경계가 모호하다. N-티어 구조니, MVC 모델이니 하는 말은 지겹도록 많이 들었지만, 막상 프로그래밍에 그런 구조를 응용하려면 생각대로 잘 되지 않는 경우가 많다.

이런 경우에 필요한 것은 컴포넌트를 ‘어떻게’ 만들어야 할지가 문제가 아니다. ‘어떤’ 컴포넌트를 만들어야 할지에 대한 결정이 급선무가 된다. 정립되어 있는 아키텍처는 이런 질문에 대한 답을 보여준다. 일반적으로 거의 모든 엔터프라이즈 아키텍처는 <그림 1>과 같은 3단계 계층으로 구성된다. 지겹도록 보아왔지만 실제 응용 프로그램 작성에는 그다지 도움이 되지 않았던 그림이다.

<그림 1> 3-티어 아키텍처

사실, <그림 1>은 CBD 기반 개발에서 어떠한 컴포넌트를 작성해야 하는지를 명확하게 보여준다. 애플리케이션 개발에서 약식 구조도로 가장 많이 사용되는 <그림 1>은 응용 프로그램이 재사용성을 가지기 위해 어떻게 설계되어야 하는 지에서부터, 컴포넌트의 동작범위, 컴포넌트의 종류, 컴포넌트의 개수까지도 개념 차원에서 정리할 수 있는 아키텍처 구조이다. 응용 프로그램 작성에서 그다지 도움이 되지 못했던 이유는 개발자들이 각 레이어에 존재하는 컴포넌트들의 역할을 명확하게 파악하지 못해서이다.

개발자들은 각각의 레이어가 소프트웨어 컴포넌트의 논리적인 구분이라는 것을 알고 있다. 하지만 그 ‘논리적인’ 이라는 것의 사전적 의미(특히 한글로 번역했을 때의 모호함)와 조금의 거리가 있기 때문에 이러한 구조에 처음 접해보는 개발자는 <그림 1>의 아키텍처에서 프리젠테이션 레이어에 위치하는 컴포넌트를 어떻게 작성해야 하는지 감이 잘 잡히지 않기 마련이다.

역할 구분이 모호한 컴포넌트 문제
어떤 컴포넌트가 어디까지의 역할을 담당해야 하는지에 대한 구분이 모호할 때, 다음과 같은 컴포넌트가 나올 수도 있다. 실제로 <리스트 1>과 같이 작성되어 동작하는 컴포넌트를 본 적이 있고 사용도 하고 있다.

 <리스트 1> 프리젠테이션, 비즈니스 엔티티, 데이터 서비스가 혼재된 UI 컴포넌트

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI.WebControls;

// 전체 프로젝트에서 공용으로 사용하는 컨트롤 네임스페이스
namespace CommonControls {
// 이름을 보면 알 수 있다. 학과 정보를 표시하는 드롭다운 리스트이다.
// 드롭다운 리스트 클래스를 상속하여 확장했다.
  public class DepartmentDropDownList : DropDownList {
    DataSet myDataSet = new DataSet();
    // 객체 생성자
    // 데이터베이스에 액세스하여 데이터를 자신에 바인딩한다.
    public DepartmentDropDownList () : base() {
      SqlDataAdapter myAdapter = new SqlDataAdapter("Select objectID, objectValue from objects", new SqlConnection());
       myAdapter.Fill(myDataSet);

       this.DataSource = myDataSet.Tables[0];
       this.DataBind();
    }

    protected override void OnSelectedIndexChanged(EventArgs e) {
       ...
    }
  }
}

작성한 컴포넌트를 눈여겨 살펴보면 3계층의 모든 요소들이 하나의 논리적/물리적인 컴포넌트에 모여 있음을 알 수 있다. 이렇게 작성된 컴포넌트를 웹 페이지에 끼워 넣고 동작시켜도 전혀 문제가 없다. 이런 기법은 많이 쓰여 왔고, 하나의 컴포넌트가 모든 동작을 다 구현함으로서 구조가 간단해지고 사용자 인터페이스(이하 UI) 컨트롤을 가져다 쓰는 페이지의 코드를 줄일 수 있다는 장점 등도 가질 수 있다.

간단한 프로그램을 작성할 때, 확장성을 고려하지 않은 소규모의 UI 중심 응용 프로그램을 작성할 때는 이런 프로그래밍 방식도 고려해 볼만하다. 하지만 기업형 응용 프로그램이라면 당연히 이야기가 달라진다. 기업형 응용 프로그램에서 앞의 구조로 프로그램을 진행한다는 것은 위험요소가 아주 많은, 재사용성을 전혀 고려하지 않은 위험한 방법이다.

이와 같은 드롭다운 리스트 컨트롤을 네임 스페이스 이름에 걸맞게 공용 컴포넌트로 선언했다고 가정해 보자. 필자가 본 실제 동작하며 현업에 사용되는 <리스트 1>과 같은 구조의 UI 컴포넌트가 사용된 시스템은 꽤 큰 규모의 사원 관리 시스템이었는데, 새 사원이 가입했을 때 사원의 급여를 책정하는 페이지에는 사원의 국민연금 가입여부, 사학 연금 가입 여부, 의료 보험 등급 등을 결정하는 10여 개의 드롭다운 리스트가 포함되어 있었다.

문제점을 살펴보자. 우선, 데이터베이스에 연결하는 부분이다. 각각의 드롭다운 리스트 컨트롤은 직접 데이터베이스에 연결하는 코드를 가지고 있다.

한 페이지에 10개의 드롭다운 리스트 컨트롤이 삽입됐다는 페이지는 사원의 상세 정보를 가져오는 두세 개의 쿼리를 실행하는 연결 이외에 각각의 드롭다운 리스트가 가진 데이터베이스 연결을 가지고 있다. 최소한 하나의 페이지가 11개의 데이터베이스 연결을 가지고 있다는 뜻이 된다.

<리스트 1>과 같이 구성된 컴포넌트가 페이지에 삽입됐다면 하나의 데이터베이스 연결이 여러 개의 쿼리를 실행하여 각각의 컨트롤에 바인딩하는 아주 당연한 연결 기법을 사용할 수 없게 된다. 데이터베이스 연결이 커넥션 풀링을 사용하고 있다면, 10명 정도의 사용자가 같은 페이지에 액세스하여 여러 개의 페이지 리로드를 10번씩만 하면 100개의 커넥션이 생기게 된다. 커넥션 풀에 각각의 커넥션이 30초 동안 유지된다고 하면, 각 사용자가 드롭다운 리스트의 항목을 바꾸면서 작업을 계속하게 되면, 웬만한 커넥션 풀은 꽉 찬 커넥션을 감당하지 못하고 뻗게 된다.

다음 문제는, 각각의 컨트롤이 상호작용하도록 프로그램을 구성하기가 힘들다는 점이다. 만약 사용자가 국민연금에 가입하고 있다면 사학연금에는 가입하지 못한다. 하나의 드롭다운 리스트가 사용자의 연금 종류를 표시하고 있다. 다른 드롭다운 리스트가 국민연금의 연금 보험료를 표시하고 있다면, 또는 연금 종류를 표시하는 드롭다운 리스트의 항목이 변경되면 컨트롤을 통째로 바꿔야 한다.

실제로 동작하는 프로그램은 페이지에 드롭다운 리스트를 40개쯤 포함시켜 놓고 컨트롤의 항목이 변경되면 컨트롤의 Visible 속성을 변경하여 숨기고 보여주고 하는 기법을 사용했다. 페이지가 무거워질 뿐더러, 데이터베이스 커넥션 풀에서의 문제는 더욱 심각해지고, 리소스를 낭비하게 된다.

전체 응용 프로그램에서 드롭다운 리스트가 표현해야 하는 항목이 한두 개 밖에 되지 않는다면 <리스트 1>과 같은 기법을 사용해 볼 수도 있겠지만, 수많은 항목을 표현하는 드롭다운 리스트가 필요한 기업형 응용 프로그램에서는 이와 같은 방법을 써서는 전체 응용 프로그램이 너무 무거워지고, UI 컴포넌트의 개수가 너무 많아질 뿐더러 여러 문제가 발생하게 된다. <리스트 2>는 문제를 조금 개선한 드롭다운 리스트이다.

 <리스트 2> 개선된 드롭다운 리스트 컨트롤

public class DepartmentDropDownList : DropDownList {
  string tableName = "";
  DataSet myDataSet = new DataSet();
// 객체 생성자
// 객체가 생성될 때 페이지에서 지정한 테이블에 액세스할 수 있도록 코드가 수정되었다.
  public DepartmentDropDownList () : base() {
    SqlDataAdapter myAdapter = new SqlDataAdapter("Select objectID, objectValue from "+ this.tableName, new SqlConnection());
    myAdapter.Fill(myDataSet);

    this.DataSource = myDataSet.Tables[0];
    this.DataBind();
}

// 컨트롤이 액세스할 테이블의 이름을 컨트롤이 삽입될 페이지에서 지정할 수 있도록 한다.
public string TableName {
  get {return this.tableName;}
  set {this.tableName = value;}
}
  ...
}

이 코드를 수정했다고 해도, 근본적인 문제는 해결되지 않는다. 데이터베이스 연결 및 커넥션 풀 문제는 여전히 지속되며, 컨트롤 간의 상호작용을 구성하기 위해서는 상당히 많은 라인의 코드를 작성해야만 한다. 하고 싶은 말은 이와 같은 방법으로 프로그래밍하면 안 된다는 것이다.

역할 구분은 분명히 해야 한다
엔터프라이즈 솔루션 아키텍처는 여기서 출발한다. 조금 냉정하게 말하자면, 지금까지 언급한 코드를 쓴 개발자라면 객체지향의 기본부터 다시 시작해야 할 것이다. 엔터프라이즈 아키텍처는 기본적으로 MVC의 구조를 가지고 있어서 모델(객체)과 뷰(컨트롤)의 역할이 완벽하게 구분돼야 한다.

3-티어 아키텍처를 기반으로 프로그래밍한다면, <리스트 1>과 <리스트 2>에서 작성한 컨트롤의 동작은 3단계 계층에 존재하는 컴포넌트를 모두 사용해야 한다는 결론이 나온다. 각 레이어에는 <그림 2>와 같은 개체(컴포넌트)들이 동작해야 한다.

<그림 2> 3계층 구조로 개선한 프로그래밍

보기에 컴포넌트 구조가 복잡해지고 컴포넌트 토폴로지가 증가할 것 같지만 이 구조를 따라 프로그래밍을 한다면 앞서 언급한 모든 문제가 해결된다. 우선 데이터베이스 연결 문제는 모든 데이터베이스의 연결을 데이터 레이어 계층의 컴포넌트에 위임함으로써 하나의 연결을 사용한다거나 또는 풀링을 사용하지 않는 등 일관된 정책을 유지할 수 있다. 또한 모든 드롭다운 리스트는 바인딩되기 전까지 데이터를 표현하는 DataSet 개체로부터 자유로우므로 드롭다운 리스트 간의 상호작용을 구성할 수 있다.

이런 3-티어 아키텍처는 응용 프로그램의 구성에 있어 많은 탄력성을 제공해 줄 수 있다. 페이지의 변경을 감시하는 옵저버(observer)나 UI 컴포넌트들의 상호작용을 관리해주는 메디에이터(mediator)를 구성한다거나 하는 등의 설계 계선을 꾀할 수 있다. 따라서 유연한 구조의 재사용성과 교체 가능성, 유지 보수성이 뛰어난 응용 프로그램을 만들 수 있게 된다.

엔터프라이즈 솔루션 아키텍처를 기반으로 한 응용 프로그램을 개발하기 위해 개발자는 다음 사항을 반드시 지켜야 한다.

◆ 수직적 관계의 모든 컴포넌트들은 느슨한 결합 관계를 가져야 한다. 예를 들면, 프리젠테이션 계층에 존재하는 특정 컨트롤이 자신을 표현하는 데이터를 가지고 있거나 다른 특정한 컴포넌트를 반드시 필요로 하는 구조여서는 안 된다.

◆ 비즈니스 컴포넌트는 자신을 보여줄 수 있는 공통된 방법을 가지고 있어야 한다. 비즈니스 계층의 컴포넌트에 포함된 개체가 프리젠테이션 계층에서 보여져야 할 경우 특정한 일관된 규칙을 가져 같은 방법으로 보여질 수 있어야 한다.

◆ 데이터베이스 연결은 일관되며 공통된 규칙을 지킬 수 있도록 집중화되어야 한다. 모든 비즈니스 컴포넌트들은 일관된 규칙으로 데이터베이스에 액세스할 수 있도록 구성되어야 한다.

컴포넌트란?  
이전 버전의 MS 환경에서는 OCX이건 액티브X이건 모듈이건 간에 독립적으로 컴파일된 유닛은 모두 컴포넌트라고 불러서 현재 문맥에서 말하는 컴포넌트가 어떤 것을 말하는지 헷갈릴 때가 많았다. 닷넷 환경이 나오면서 이러한 혼란을 줄이기 위해 어셈블리(assembly)라는 용어를 등장시켜 문맥상에서 말하는 컴포넌트의 종류를 쉽게 구분할 수 있도록 했다.

일반적으로 컴포넌트라는 용어는 전체 솔루션에서 일부분이거나 또는 각각의 조각(부품)이라는 의미로 사용된다. 어셈블리나 액티브X 같은 컴파일된 유닛, 웹 페이지, 웹 서비스, 비즈토크 오케스트레이션 등 모든 결합될 수 있는 응용 프로그램의 부품을 컴포넌트라고 부른다.

어떤 컴포넌트를 만들어야 하는가?
3-티어 아키텍처로는 2% 부족하다고 느낄 것이다. 응용 프로그램 또는 서비스는 다른 종류의 작업을 수행하는 여러 컴포넌트들로 구성된다. 복잡한 기업형 응용 프로그램이라면 수백 개의 컴포넌트로 구성된다. 하지만 앞에서 살펴봤듯이 수없이 많은 컴포넌트는 사용자의 요구와 관계없이 비슷한 종류의 컴포넌트로 구성되는 경우가 대부분이다. 3-티어 아키텍처는 그런 비슷한 종류의 컴포넌트를 구별할 수 있게 하지만, 조금 더 세분화하여 분류할 수 있다.

<그림 3>은 완벽히 분류되지는 않았지만, 대부분의 대규모 응용 프로그램에서 공통적으로 사용되는 일반적인 컴포넌트의 유형을 보여준다. 그다지 친절해 보이지는 않지만, 자세히 들여다보면 응용 프로그램의 아키텍처를 어떻게 구성해야 하는지를 한눈에 보여주는 그림이라 할 수 있다(MSDN 웹사이트에서는 이러한 그림이나 문서를 쉽게 찾아볼 수 있다).

<그림 3> 컴포넌트의 종류

<그림 3>은 대규모의 응용 프로그램에서 공통으로 사용되는 일반적인 컴포넌트의 유형을 잘 설명하고 있다. 한 장의 그림이긴 하지만 각각의 컴포넌트가 어떻게 동작해야 하느냐에 대한 물음, 예를 들면 UI 프로세스 컴포넌트 단계를 구성하는 컴포넌트는 어떤 동작을 해야 하는지, 어떻게 구성돼야 하는지에 관련된 의문을 명확하게 설명해 주지는 못한다. MSDN 웹사이트나 gotdotnet 웹사이트에서 설명을 찾아볼 수 있다. UI 컴포넌트가 어떤 구성을 가져야 하는지, 어떤 컴포넌트여야 하는지에 대한 설명은 다음과 같다.

"대부분의 솔루션들은 사용자가 애플리케이션과 상호작용할 수 있는 방법을 제공할 필요가 있다. 소매 애플리케이션의 예제에서 고객들은 웹 사이트를 통해 상품을 보고 주문할 수 있다. 판매 대리인은 윈도우 운영체제 기반의 애플리케이션을 이용해 회사로 전화를 건 고객의 주문 데이터를 입력할 수 있다. 사용자 인터페이스는 사용자를 위한 데이터 포맷을 지정하고 제시하며 사용자들로부터 입력될 데이터를 요구하고 검증하기 위해 윈도우 폼, ASP.NET 페이지, 컨트롤 혹은 그 밖의 다른 기술을 이용해서 구현된다."

아키텍처에 대한 기본 지식이 있는 개발자라면 이 간단한 설명으로도 이해할 수 있겠지만, 아키텍처 기반 개발 경험이 없는 개발자이거나 처음 공부하는 사람이라면 더 헷갈릴 수 있는 가능성이 크다. 필자가 강의를 진행할 때(특히 디자인 패턴이나 추상화 기법들을 강의할 때) 수강자들이 어려운 이론을 들으면 반드시 하는 말이 "코드를 보여주세요"라는 것이다. 정말, ‘백문이 불여일견’이라는 속담이 여기서도 통하게 되는데, 백번 말로 설명해도 이해 안가는 경우에는 아키텍처를 기반으로 작성된 애플리케이션의 예제를 한번 보는 것이 설명을 백번 듣는 것보다 훨씬 더 도움이 된다.

MS는 이전에 J2EE 기술과 성능 비교에서 펫샵(Petshop) 응용 프로그램을 사용해 자사의 환경 성능을 테스트했는데, 이 펫샵이라는 응용 프로그램은 엔터프라이즈 솔루션 아키텍처에 기반하여 작성되었다. 말이나 그림으로 잘 이해되지 않는 독자라면 당장에 MS 웹사이트에 접속해 펫샵 3.0을 컴퓨터에 설치하고 코드를 살펴보기 바란다.

이해가 가는 독자라도 코드를 보는 것이 훨씬 도움이 된다. 물론, 코드만 본다고 되는 것은 아니고 그림과 코드를 비교해가면서 살펴보는 것이 좋다.

<화면 1> 닷넷 펫샵

펫샵 3.0을 설치하고 비주얼 스튜디오 닷넷을 사용해 코드를 볼 준비가 됐다면, 이제부터 아키텍처에 기반한 애플리케이션이 어떻게 작성되고 동작하며, 각 컴포넌트는 어떻게 작성되고 구성되며 어떤 역할을 해야 하는지를 알아보도록 하자. 이번 연재에서는 프리젠테이션 계층 컴포넌트가 어떻게 구성되는지에 대해 알아본다(비즈니스 컴포넌트와 데이터 액세스 컴포넌트는 다음 호에 알아보도록 한다)

프리젠테이션 계층 구조와 설계
<그림 2>에 나타나 있는 구조에서 프리젠테이션 계층을 구성하는 부분은 <그림 4>와 같다. 프리젠테이션 계층에는 UI 컴포넌트와 UI 프로세스 컴포넌트, 두 종류의 컴포넌트가 존재한다.

<그림 4> 프리젠테이션 계층 컴포넌트

UI 컴포넌트는 말 그대로 사용자와 상호 작용하는 객체들이 모인 물리적 객체 집합이다. 이 계층에는 System.Windows.Forms 네임스페이스의 개체들과 System.Web.UI 네임스페이스의 개체들이 존재한다. 웹 기반 응용 프로그램이라면 TextBox 컨트롤, Button 컨트롤, DropDownList 컨트롤, DataGrid 컨트롤 등 ASP.NET 페이지에 직접 붙여 넣는 컨트롤들이 모두 이 범주에 속한다고 볼 수 있다.

UI 컴포넌트
UI는 크게 세 가지 역할을 수행해야 한다. 첫 번째 역할은 사용자에게 데이터를 보여주는 것이다. 예를 들면 현재 로그인한 사용자의 이름을 보여주기 위해 Label 컨트롤을 사용하거나 상품의 목록을 보여주기 위해 DataGrid 컨트롤을 사용하는 등이다.

여기서 주의해야 할 점은 Label 컨트롤에 로그인한 사용자의 이름을 보여주기 위해 Label 컨트롤을 수정해 Label 컨트롤이 직접 HttpContext 개체나 Cookie 등에 액세스하지 않는다는 것이다. Label 컨트롤이나 DataGrid 컨트롤은 단지 어떤 데이터를 받아들여서 표시할 수 있는 역할만을 해야 한다.

두 번째는 사용자의 입력을 받아들이고 입력된 데이터를 하위 컴포넌트에 전달하는 역할이다. 입력된 데이터를 하위 컴포넌트에 전달하기 위해서는 함수를 호출해야 한다. 하지만 사용자의 이벤트를 받아들이는데 있어 가장 많이 사용되는 버튼 컨트롤이 이러한 함수를 직접 호출하는 형태여서는 안된다. 사용자의 입력을 받아들이는 UI 컴포넌트는 단지 속성과 이벤트만을 가지고 있을 뿐, 직접적인 함수의 호출 로직을 가지는 것은 금물이다.

세 번째는 입력된 데이터를 사용자가 검증하는 역할이다. 사용자가 입력한 데이터가 정수형인지, 날짜인지 등의 애플리케이션 내에서 유효한 값인지를 검증할 수 있는 클라이언트 수준에서의 로직이 필요하다. 종합해 보면, 로그인한 사용자의 이름을 웹 페이지에 보여주는 UI는 <그림 5>와 같이 작성되어야 한다.

<그림 5> 사용자 인터페이스 설계

UI 컴포넌트는 사용자에게 데이터를 보여주고 사용자 입력 데이터를 검증하고 백앤드로 요청하며 사용자가 데이터에 수행하고자 하는 작업을 알려주는 등 사용자 행위를 검증하는 역할을 담당한다. 그리고 또 중요한 것은 UI는 사용자가 특정 시점에 특정 작업만을 수행하도록 행위를 제한할 수 있어야 한다. 다시 종합하여 생각해보면 UI 컴포넌트는 다음과 같은 사항들을 준수해야 한다.

◆ 트랜잭션을 생성하거나 또는 트랜잭션에 참여하거나 트랜잭션을 지원해서는 안된다.

◆ 데이터를 표시하기 위해서는 UI 프로세스 컴포넌트를 참조하거나 하위 계층의 컴포넌트를 참조해야 하지 자신이 직접 데이터를 포함하고 있으면 안된다.

◆ 사용자에게 데이터를 받아들이는 작업을 위한 시각화된 신호와 검증을 지원해야 하며 사용자의 요구에 적절한 컨트롤을 제공한다.

◆ 사용자 이벤트가 발생됐을 때 이를 컨트롤러에 전달하여 컨트롤러의 함수를 호출할 수 있어야 한다.

참조 구현 모델인 펫샵에서 UI가 어떻게 구성되어 있는지 살펴보도록 하자. 펫샵은 <그림 6>과 같은 로그인 사용자 인터페이스를 가지고 있다.

<그림 6> 닷넷 펫샵의 로그인 UI

로그인 사용자 인터페이스를 구성하는 Signin.aspx 페이지의 뷰를 구성하는 html 소스의 일부는 <리스트 3>과 같다.

 <리스트 3> Signin UI의 View를 구성하는 html 코드

<%@ Register TagPrefix="PetsControl" TagName="NavBar" Src="Controls/NavBar.ascx" %>
<%@ Page Language="c#" CodeBehind="SignIn.aspx.cs" Inherits="PetShop.Web.SignIn" EnableSessionState="true" AutoEventWireup="false" %>
<HTML>
  <HEAD>
    ....
  <!--사용자 입력을 받아들이는 Form -->
<form id="frmSignIn" runat="server" method="post">
    <table cellpadding="1" cellspacing="0">
      <tr>
      <td class="label">User ID:</td>
        <td>
        <! -- 적절한 사용자 입력 컨트롤 -->
<asp:textbox id="txtUserId" runat="server" text="DotNet" columns="15" maxlength="20" />
<!-- 입력된 값 검증 -->
<asp:requiredfieldvalidator id="valUserId" runat="server" controltovalidate="txtUserId" errormessage="Please enter user ID." enableclientscript="False" />
        </td>
       </tr>
       <tr>
       <td class="label">Password:</td>
        <td>
<asp:textbox id="txtPassword" runat="server" value="DotNet" columns="15" maxlength="20" textmode="Password" />
<asp:requiredfieldvalidator id="valPassword" runat="server" controltovalidate="txtPassword" errormessage="Please enter password." enableclientscript="False" />
        </td>
      </tr>
    </table>
    <!-- Controller의 함수 호출 -->
  <p><asp:imagebutton id="btnSubmit" runat="server" onclick="SubmitClicked" imageurl="Images/buttonSubmit.gif" alternatetext="Submit" />
</form>

닷넷 펫샵의 로그인 UI의 컨트롤러는 Signin.aspx.cs 페이지에 구성되었다. 페이지는 View에 포함된 버튼 컨트롤이 클릭 이벤트를 발생하여 컨트롤러의 이벤트 핸들러 함수를 호출하면 사용자가 입력한 아이디와 패스워드를 알맞은 문자열로 검증하고 Business Process Component의 함수를 호출하여 로그인을 처리한다. 아키텍처에 기반한 '제대로 된' 처리를 보여준다.

 <리스트 4> Signin UI의 컨트롤러(Page 객체를 상속하는 코드 비하인드 코드이다)

protected void SubmitClicked(object sender, ImageClickEventArgs e) {
    if (Page.IsValid) {
    // Get the user info from the text boxes
    string userId = WebComponents.CleanString.InputText(txtUserId.Text, 50);
    string password = WebComponents.CleanString.InputText(txtPassword.Text, 50);

    // Hand off to the account controller to control the naviagtion
    ProcessFlow.AccountController accountController =
new ProcessFlow.AccountController();

    if (!accountController.ProcessLogin(userId, password)){
        // If we fail to login let the user know
        valUserId.ErrorMessage = MSG_FAILURE;
        valUserId.IsValid = false;
    }
}
}

UI 프로세스 컴포넌트
UI 프로세스 컴포넌트는 사용자와의 상호작용이 단계적인 흐름을 예측할 수 있도록 구성될 때 유용하다. 로그인 UI의 경우 로그인을 시도한 사용자가 유효한 회원이 아닌 경우 에러 메시지를, 유효한 회원이 로그인 했을 경우 요청한 페이지로 이동한다는 단계적인 흐름을 간단히 예측할 수 있다.

또 사용자가 물품을 구매하는 프로세스의 경우 쇼핑카트에 물품을 입력하고, 전체 가격을 보여주고, 지불에 관한 정보를 입력하도록 한다. 그런 다음 배송 주소 정보를 입력하도록 요구하고 쇼핑 카트에 담긴 상품을 마이그레이션한 후 주문 정보를 입력한다.

프로세스 내에서 단계가 유지될 때 주문된 상품이 무엇이고 주문한 사용자는 누구이며 신용카드의 정보는 어떤 것인지에 대한 프로세스의 상태를 유지 관리할 필요가 있다. 이런 경우 UI 프로세스 컴포넌트를 작성하여 사용자의 프로세스 조절을 중앙 집중화할 수 있다. 특히, 다중 UI 페이지일 경우 더욱 유용하게 사용될 수 있다.

UI 프로세스 컴포넌트는 UI처럼 HTML로 구성되거나 페이지를 상속받는 개체로 구성되지 않는다. 일반적으로 UI에서 호출되는(주로 컨트롤러에서 호출되는) 메쏘드를 지닌 닷넷 클래스로 구성된다.

UI 프로세스 컴포넌트의 작성은 상당히 어렵다. UI 프로세스 컴포넌트가 제공할 만한 추상화가 필요한지를 주의 깊게 생각하지 않으면 UI 프로세스 컴포넌트의 사용은 오히려 프로젝트의 진행에 장애 요소가 될 수 있다. 하지만 주의 깊은 추상화로 UI 프로세스 컴포넌트를 구성하면, 다음과 같은 여러 장점들을 꾀할 수 있다.

◆ 사용자 상호작용의 상태를 지속할 수 있다.

◆ 다중 사용자 인터페이스를 쉽게 디자인하고 구성할 수 있다.

◆ 같은 사용자 프로세스가 여러 사용자 인터페이스에 재사용될 수 있다.

◆ 애플리케이션의 유지 관리 탄력성을 얻을 수 있다.

◆ 다른 복잡한 작업들을 쉽게 추가할 수 있게 해 준다.

UI 컴포넌트는 일반적으로 다음과 같은 기능을 구현해야 한다. 대규모 기업형 애플리케이션이라면 대부분이 반드시 필요한 기능들이며, 아키텍처에 기반하지 않고 개발된 응용 프로그램들은 이런 반드시 필요한 기능들을 여러 계층에 분산시켜 놓기 마련인데, 이런 기능들을 가지는 컴포넌트를 생성하여 중앙 집중과 관리함으로서 얻을 수 있는 이점은 무궁무진하다 할 수 있다.

◆ UI 컴포넌트는 UI와 연동되는 데이터의 흐름이다. 컨트롤의 로직들을 재개발하지 않고 상호작용 흐름을 연동할 수 있는 제어를 제공해야 한다.

◆ 논리적인 사용자와 시스템의 상호작용 흐름을 제어 추상화하여 UI 프로세스 컴포넌트를 사용하는 구현으로부터 분리되도록 작성돼야 한다.

◆ 예외 발생시에 사용자 프로세스의 흐름을 제어할 수 있어야 한다.

◆ 사용자의 상태에 대한 현재 상태를 유지해야 한다.

◆ 일반적으로 비즈니스 컴포넌트 또는 데이터 액세스 로직과 연동하여 내부 상태를 초기화하도록 한다.

◆ 사용자 상호작용에 의해서 영향을 받는 비즈니스 엔티티를 저장하는 비즈니스 상태를 유지할 수 있도록 한다.

◆ 엔터프라이즈 서비스로 구성하지 않는다.

UI 프로세스 컴포넌트는 <그림 7>과 같이 설계돼야 한다. 그럼 닷넷의 구현 모델을 살펴보도록 하자. <리스트 5>는 닷넷 펫샵의 UI 프로세스 컴포넌트 중 로그인를 처리하는 프로세스 컴포넌트의 소스코드이다.

<그림 7> UI 프로세스 컴포넌트 디자인

 <리스트 5> 펫샵의 로그인 UI 프로세스 컴포넌트 코드의 일부

private const string ACCOUNT_KEY = "ACCOUNT_KEY";
private const string URL_DEFAULT = "default.aspx";
private const string URL_SIGNIN = "SignIn.aspx";
private const string URL_ACCOUNTCREATE = "MyAccount.aspx?action=create";
private const string URL_ACCOUNTSIGNIN = "MyAccount.aspx?action=signIn";
private const string URL_ACCOUNTUPDATE = "MyAccount.aspx?action=update";

...

public bool ProcessLogin(string userId, string password){
    // 비즈니스 로직 레이어 개체를 사용하여 로그인을 처리한다.
    Account account = new Account();
    AccountInfo myAccountInfo = account.SignIn(userId, password);

    // 로그인이 성공하면 상태를 세션에 저장하고 요청 페이지로 리디렉션한다.
    if (myAccountInfo != null) {
        HttpContext.Current.Session[ACCOUNT_KEY] = myAccountInfo;

        // 사용자를 어떤 페이지로 보낼 것인지를 결정한다.
        If (FormsAuthentication.GetRedirectUrl(userId,
false).EndsWith(URL_DEFAULT)) {
            FormsAuthentication.SetAuthCookie(userId, false);
            HttpContext.Current.Response.Redirect(URL_ACCOUNTSIGNIN, true);

        }else{
            // 사용자를 페이지로 되돌려 보낸다.
            FormsAuthentication.SetAuthCookie(userId, false);
            HttpContext.Current.Response.Redirect(
FormsAuthentication.GetRedirectUrl(userId, false), true);
        }
        return true;

    }else {
        // 로그인이 실패했을 경우
        return false;
    }
}

사용자가 로그인 프로세스를 거친 후 어떤 동작을 제어하는 <리스트 5> 이외에도 UI 프로세스 컴포넌트가 수행해야 하는 동작들에 대한 구현 코드가 들어 있다. 독자들은 펫샵 3.0을 설치했을 것이므로 상세 구현 코드는 생략하겠다.

 <리스트 6> 로그인 UI 프로세스 컴포넌트의 구현

// 사용자 객체 생성
public bool CreateAccount(AccountInfo newAccountInfo){}

// 사용자 상태 갱신
public void UpdateAccount(AccountInfo updatedAccountInfo){}

// 로그인한 사용자의 정보를 알아냄
public AccountInfo GetAccountInfo(bool required){}

// 사용자가 자주 이용하는 카테고리를 알아냄
public string GetFavouriteCategory(){}

// 로그아웃 처리
public void LogOut(){}

아키텍처 설계는 쉬운 일이 아니다
이 글을 읽은 독자는 다음과 같이 반문할 지도 모르겠다.

"결국은 객체지향에서 이야기하는 추상화이고 디자인 패턴이네요?"

답은 다음과 같다.

"그렇죠. 사실 추상화 수준과 디자인 패턴을 모르고서는 이를 이해하기 힘듭니다."

아키텍처를 설계한다는 것은 정말 쉬운 일이 아니다. 잘못 설계된 아키텍처는 언젠가 문제를 일으키기 마련이고, 프로젝트를 겉잡을 수 없는 방향으로 이끌게 된다. 아키텍트를 꿈꾸는 개발자라면, 당장에 아키텍처를 설계해 보겠다고 겁 없이 덤비기보다는 신뢰성 있는 기업에서 작성해서 발표한 아키텍처 구조를 잘 살펴보고 이해한 후 차근차근히 공부하는 자세가 필요하다. 한강의 기적이 하루아침에 이루어진 것이 아니듯이, 실력 있는 아키텍트와 제대로 된 아키텍처는 하늘에서 뚝 떨어지는 것이 아니다.

다음 글에서는 비즈니스 로직 컴포넌트와 데이터 액세스 로직 컴포넌트의 구성과 사용에 대해 알아볼 것이다. 마지막으로 이번 연재를 토대로 ‘닷넷 엔터프라이즈 솔루션 아키텍처’를 주제로 한 세미나를 준비 중에 있다. 좀 더 상세한 자료를 원한다면, http://www.nogatech.net에서 다운받을 수도 있다.@

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.

+ Recent posts