본문 바로가기

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

개발 프레임워크 만들기 대장정 46 - 서비스 호출하기

이제 클라이언트에서 달봉이가 만들어놓은 프락시 팩토리를 이용해서 서비스를 호출해보자.

 

■ 서비스 참조 추가하기

 

우선 서비스에 대한 참조를 클라이언트 프로젝트에서 추가하자. BONG.WIN.CO.UserMgmt 프로젝트의 References 노드를 오른쪽 클릭해서 “Add Service Reference…”를 선택한다.

그럼 다음과 같은 서비스 참조 추가 창이 뜬다.

Address 박스에 이전 포스트에서 봤던 주소를 복사해 넣는다.

그런 다음 “Go”버튼을 클릭한다. 그럼 앞의 그림처럼 SampleService가 보이게 된다. 이제 Namespace 텍스트박스에 “SampleAsyncService”라 입력한다.

그리고 마지막으로 “Advanced…”버튼을 클릭한다.

그래서 Generate aysnchronous operations 체크박스를 선택한다.

이렇게 해서 참조 추가를 마친다. 이렇게 하면 Visual Studio는 클라이언트에서 사용할 수 있는 SampleService를 동기와 비동기적으로 호출할 수 있는 메소드를 갖는 프락시 클래스를 자동으로 생성해준다. 달봉이 프락시 생성 팩토리는 이 자동 생성 프락시를 사용하게 된다.

 

■ 업무 화면 준비

 

이제 업무 화면에 버튼을 하나 올려놓고 버튼을 클릭했을때 실행될 이벤트 핸들러를 작성해보자. UserMgmt.xaml 마크업 코드는 다음과 같이 되어 있다.

<Bong:BongControlBase x:Class="BONG.WIN.CO.UserMgmt.UserMgmt"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:Bong="clr-namespace:Bong.Win;assembly=Bong.Win"

    Height="Auto" Width="Auto">

    <Border BorderBrush="Black" BorderThickness="2">

        <StackPanel>

            <Button Height="23" Name="btnHello" Width="75" Click="btnHello_Click">Hello</Button>

            <TextBlock Name="tbReply"></TextBlock>

        </StackPanel>

    </Border>

</Bong:BongControlBase>

 

■동기호출

 

이제 달봉이가 만들어놓은 프락시 팩토리 객체를 이용해서 서비스를 호출할 차례다.

먼저 동기로 호출하는 코드이다. 이것은 심플하다.

 BongServiceProxyFactory<SampleAsyncService.ISampleService> sampleProxyfactory = null;

SampleAsyncService.ISampleService asyncServiceProxy = null;

 string strHello = "";

 private void btnHello_Click(object sender, RoutedEventArgs e)

{

     //동기호출

     this.tbReply.Text = "";

     try

     {

         sampleProxyfactory = new BongServiceProxyFactory<SampleAsyncService.ISampleService>();

         asyncServiceProxy = sampleProxyfactory.GetServiceProxy("BaseUrlOfCOService",

             "BONG.CO.UserMgmt.Service/SampleService.svc"); //, this);

         strHello = asyncServiceProxy.Hello();

     }

     catch

     {

         //예외 처리

     }

     finally

     {

         sampleProxyfactory.Close();

     }

     //결과값 이용

     this.tbReply.Text = strHello;

 

BongServiceProxyFactory<T>를 생성한다. 개발자가 사용할 서비스에 대한 프락시 객체를 생성해줄 공장(?)이다. 그 다음 그 팩토리 객체를 이용해서 SampleService에 대한 주소를 건네주고 서비스에 대한 프락시 객체를 건네받는다.

이때 마지막 인자로 this를 넘겨줘도 상관없지만 내부적으로는 서비스 시작과 끝을 알리는 이벤트가 발생하지만 겉으로 보기에는 아무일도 일어나지 않는다. 프로그레스바도 나타나지 않는다. 왜? 동기호출이니까. (물론 동기 호출이라도 프로그레스바를 보여주는 방법은 있다. 프로그레스바를 보여주는 또다른 쓰레드를 만들어서 사용할 수도 있지만, 이렇게 프로그레스바를 보여주는 방법은 그닥 사용자가 보기에는 좋지 않다. 어플리케이션과 프로그레스바가 따로 따로 논다. 좋지 않다.)

서비스 프락시 객체 asyncserviceProxy에서 점을 찍으면 인텔리센스 기능에 의해 다음과 같은 호출 가능한 메소드 후보들이 나타난다.

이 중에서 Begin, End로 시작하는 Hello() 버전은 동기를 위한 것이고, Hello()가 동기 호출을 위한 것이다.  Hello() 를 선택한다. 이제 서비스를 호출하고 결과값을 반환받아서 필요한대로 사용하면 된다. 코딩은 앞에서처럼 try~catch~finally식으로 하면 된다.

 

■ 비동기 호출

 

다음은 비동기 호출을 위한 코딩 패턴이다. 개발자가 비동기 호출을 할때는 서비스를 호출하는 부분과 결과값을 처리하는 부분을 분리해서 작성해야 한다.

복잡한 듯 보이지만 다음 패턴대로만 한다면 그렇게 복잡한 것도 아니다.

BongServiceProxyFactory<SampleAsyncService.ISampleService> sampleProxyfactory = null;

SampleAsyncService.ISampleService asyncServiceProxy = null;

string strHello = "";

private void btnHello_Click(object sender, RoutedEventArgs e)

{

    //비동기호출

    //서비스 호출하는 부분

    this.tbReply.Text = "";

    sampleProxyfactory = new BongServiceProxyFactory<SampleAsyncService.ISampleService>();

    asyncServiceProxy = sampleProxyfactory.GetServiceProxy("BaseUrlOfCOService",

        "BONG.CO.UserMgmt.Service/SampleService.svc", this);

    AsyncCallback callback = new AsyncCallback(HelloCallback);

    //서비스 프락시 객체를 서버측으로 보낸다.

    asyncServiceProxy.BeginHello(callback, asyncServiceProxy);

}

이 부분이 서비스를 비동기로 호출하는 부분이다. 서비스 프락시 객체 asyncServiceProxy의 BeginHello() 메소드를 호출하고 있다. 이 메소드가 호출되고 나서 UI 쓰레드( 현재 메소드를 호출하는 쓰레드)는 이곳에서 서비스 답변을 기다리지 않는다. 그냥 호출만 하고 자신은 계속 실행을 진행한다. 앞의 코드에서는 서비스를 호출하고 나서 아무 일도 하지 않고 그냥 btnHello_Click() 메소드가 종료될뿐이다. 이때 내부적으로는 쓰레드가 하나 새롭게 하나 생성되어서 서비스 호출을 담당하게 된다.

BeginHello() 메소드에 두 개의 인자를 넘겨주고 있다. 서비스를 호출하는 부분에서는 서비스 답변을 기다리지 않고 그냥 종료되었으므로 그 결과를 처리할 부분을 프레임워크에 알려줘야 한다. 첫번째 인자가 그 답변을 처리할 곳에 대한 정보를 제공하는 역할을 한다. HelloCallback이라는 메소드에서 비동기적으로 호출한 서비스의 답변을 기다리겠다는 의미로 HelloCallback 메소드를 프레임워크에게 알려줘야 하는데 그냥 건네주면 프레임워크는 인식할 수 없다. AsyncCallback이라는 것으로 한번 감싸서 보내줘야 한다. 첫번째 인자 callback의 의미는 그렇다.

두번째 인자로 asyncServiceProxy 객체를 넘겨주고 있는데 서비스 메소드를 호출하는 프락시 객체 자신을 넘겨주고 있다. 두번째 인자는 원래 결과값을 처리하는 곳으로 어떤 특별한 값을 보내고 싶을때 사용한다. 이곳에 값을 넘겨주면 서비스 작업 호출 후 프레임워크에서는 결과값을 받는 메소드 HelloCallback을 호출할 때 그 값을 그대로 건네준다. 이곳에서는 서비스 프락시 객체 asyncServiceProxy 자신을 그대로 콜백 함수에 넘겨주겠다는 것이다. 그럼 콜백 함수에서 하는 일을 보도록 하자.

/// <summary>

/// 결과값 처리하는 부분

/// </summary>

/// <param name="result"></param>

private void HelloCallback(IAsyncResult result)

{

    if (result.IsCompleted)

    {

        if (!this.Dispatcher.CheckAccess())

        {

            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,

            new AsyncCallback(HelloCallback), result);

            return;

        }

        try

        {

            //서버측으로 보낸 서비스 프락시 객체를 복원한다.

            asyncServiceProxy = (SampleAsyncService.ISampleService)result.AsyncState;

            strHello = asyncServiceProxy.EndHello(result);

            this.tbReply.Text = strHello;

        }

        catch (Exception e)

        {

            //예외처리

            MessageBox.Show(e.Message);

        }

        finally

        {

            sampleProxyfactory.Close();

        }

    }

}

콜백 함수 HelloCallback 메소드는 프레임워크로부터 인자를 하나 받는다 : IAsyncResult 타입의 result객체. 콜백함수에서는 서비스 호출이 종료되었는지를 확인해서 다음 작업을 해 줘야 하는데, 서비스 호출 종료는 코드에서처럼 result객체의 IsCompleted 속성을 통해서 확인할 수 있다.

그 다음 볼드체 부분에서 하는 일은 이렇다. 앞에서 서비스를 비동기로 호출하면 내부적으로 쓰레드가 하나 생성되고 그곳에서 서비스 호출이 실행된다고 했다. 그 새로운 쓰레드에서 콜백함수 HelloCallback 메소드를 호출하고 있다. 그러나 UI 컨트롤들에 접근하기 위해서는 원래의 쓰레드로 돌아와야 한다. this 즉 UserControl의 Dispatcher를 통해서 현재 호출하는 쓰레드가 UserControl이 속한 쓰레드와 일치하는지를 확인하고 있다. 만약 쓰레드가 달라서 UI 컨트롤에 접근할 수 없다고 판단되면 다시 Dispatcher의 BeginInvoke()를 호출해서 다른 쓰레드를 통해서 HelloCallback 함수를 다시 호출한다. BeginInvoke()를 호출할때 콜백함수를 인자로 넘겨주는 이유이다. 이런 작업을 UI 쓰레드에 도착할때까지 반복하는 것이다. 최종적으로 UI 쓰레드에 도착해서 this.Dispatcher.CheckAccess() 확인 작업을 통과하고 나면 이후의 코드가 실행될 수 있다.

첫번째 작업은 result 객체의 AsyncState 속성을 호출하고 있는데, 이 속성을 통해서 이전에 서비스를 호출할때 넘겨준 서비스 프락시 객체 asyncServiceProxy를 복원할 수 있다. 이 복원된 객체를 통해서 EndHello() 메소드를 호출하는데 이로써 서비스 호출 결과를 받을 수 있다. 결과를 받아서 이제 필요한대로 사용하면 된다.

처음에는 asyncServiceProxy 객체를 넘겨 받지 않고 원래의 객체에 직접 접근했었다. finally 블록에 보면 sampleProxyfactory 객체는 서비스를 호출할때 넘겨서 받지 않고 원래의 객체에 직접 접근하고 것처럼. 그랬더니 가끔가다 에러가 발생했는데, 이 에러가 항상 발생하는 것은 아니었다. 어쩌다 에러가 발생하는데 에러 내용이 뭐였는지 기억이 나지 않아서 지금 재현해볼려니까 또 발생하지 않는다. 써글… 다음에 발생하면 이 부분에 대해서 보완하도록 하겠다. 이 포스트는 여기서 끝내야 겠다.

어휴…힘들다.

다음에는 Spring.NET을 이용해서 비즈니스 레이어에서 트랜잭션을 처리하는 방법과 데이터베이스에 접근하는 방법을 해 볼까 한다.