Microsoft Windows 워크플로 파운데이션 시작하기: 개발자 연습

2006. 11. 30. 21:56 IT 및 개발/.NET FX & Visual C#
Microsoft .NET 플랫폼용 워크플로 기반 응용 프로그램을 개발하는 개발자에게 유용한 Microsoft Windows 워크플로 파운데이션의 기술과 기능을 소개합니다.

참고 : 이 기사는 Windows 워크플로 파운데이션 베타 2를 기준으로 작성되었습니다. 따라서 최종 릴리스 전에 기술상의 변경 사항이 있을 수 있습니다.

Windows 플랫폼에 워크플로 지원을 추가하기 위한 준비
Microsoft Windows 워크플로 파운데이션(WF)은 Windows 플랫폼에서 워크플로 솔루션을 개발하기 위한 확장 가능한 프레임워크입니다. 곧 소개될 Microsoft WinFX의 일부인 Windows 워크플로 파운데이션은 워크플로 기반 응용 프로그램을 개발하고 실행하는 데 필요한 API 및 도구를 제공하며, 특히 휴먼 워크플로 및 시스템 워크플로를 비롯한 응용 프로그램 범주 전반에 걸쳐 종단 간 솔루션을 구축하기 위한 통합된 단일 모델을 제공합니다.

Windows 워크플로 파운데이션은 처음부터 모든 수준에서의 확장성을 고려하여 설계한 다목적 워크플로 프레임워크입니다. Windows 워크플로 파운데이션 기반 솔루션은 Microsoft .NET 코드를 기반으로 상호 연결된 구성 요소로 이루어지며 호스트 응용 프로그램에서 실행됩니다. 맞춤 구성된 환경에서 시각적으로 웹 페이지를 작성하는 것과 마찬가지로 비주얼 디자이너에서 세부적인 워크플로 단계를 구성하고 워크플로 구성 요소에 코드를 추가하여 규칙을 구현하고 비즈니스 프로세스를 정의하게 됩니다.

Windows 워크플로 파운데이션에서는 워크플로 엔진, .NET 관리 API, 런타임 서비스, Microsoft Visual Studio 2005에 통합된 비주얼 디자이너 및 디버거를 제공합니다. 이러한 Windows 워크플로 파운데이션을 사용하여 클라이언트와 서버에 모두 적용되며 모든 유형의 .NET 응용 프로그램에서 실행 가능한 워크플로를 작성하고 실행할 수 있습니다.

이 기사에서는 Windows 워크플로 파운데이션을 간단히 소개하고 단계별 예를 통해 작동 방식을 설명합니다.

워크플로는 작업 맵으로 정의되는 휴먼 프로세스 및 시스템 프로세스 모델입니다. 작업은 워크플로 내의 단계이며 워크플로 실행, 다시 사용 및 구성의 단위입니다. 작업 맵은 규칙, 동작, 상태 및 작업 간의 관계를 표시합니다. 작업 배치를 통해 Windows 워크플로 파운데이션 워크플로의 디자인을 완료한 다음에는 이를 .NET 어셈블리로 컴파일하고 워크플로 런타임 및 CLR(공용 언어 런타임)에서 실행할 수 있습니다.

첫 번째 워크플로 만들기
Windows 워크플로 파운데이션은 주로 Visual Studio 디자이너에서 디자인 및 구현된 특수 개체를 처리하는 .NET 기반 런타임 환경으로 구성됩니다. Windows 워크플로 파운데이션을 지원하려면 Microsoft .NET Framework 2.0이 필요합니다. 그리고 Visual Studio 2005에 대해 Windows 워크플로 파운데이션 디자이너 및 프로젝트 템플릿 지원 기능을 제공하는 별도의 설치 관리자 패키지가 필요합니다. 설치한 다음에는 그림 1과 같이 Visual Studio 2005 기본 프로젝트 목록에 새로운 노드가 추가됩니다.


그림 1. Visual Studio 2005의 워크플로 프로젝트 템플릿

특정한 형식의 워크플로 응용 프로그램을 식별하는 다양한 옵션을 선택할 수 있습니다. 표 1에서는 이러한 워크플로 프로젝트 템플릿 목록을 보여 줍니다.

표 1. Visual Studio 2005의 워크플로 프로젝트 형식
형식 설명
순차적 워크플로 콘솔 응용 프로그램 기본 순차적 워크플로와 콘솔 테스트 호스트 응용 프로그램이 포함된 워크플로를 작성하는 프로젝트를 만듭니다.
순차적 워크플로 라이브러리 순차적 워크플로를 라이브러리로 작성하는 프로젝트를 만듭니다.
워크플로 작업 라이브러리 워크플로 응용 프로그램의 구성 요소로 다시 사용할 수 있는 작업 라이브러리를 작성하는 프로젝트를 만듭니다.
상태 시스템 콘솔 응용 프로그램 상태 시스템 워크플로 및 콘솔 호스트 응용 프로그램을 작성하는 프로젝트를 만듭니다.
상태 시스템 워크플로 라이브러리 상태 시스템 워크플로를 라이브러리로 작성하는 프로젝트를 만듭니다.
빈 워크플로 워크플로 및 작업을 추가할 수 있는 빈 프로젝트를 만듭니다.

Windows 워크플로 파운데이션은 기본적으로 순차적 워크플로 및 상태 시스템 워크플로라는 두 가지 기초적인 워크플로 스타일을 지원합니다.

순차적 워크플로는 마지막 작업이 완료될 때까지 각 단계가 순차적으로 실행되는 파이프라인으로 표현되는 작업에 적합합니다. 그러나 순차적 워크플로도 항상 순차적으로만 실행되는 것은 아닙니다. 즉, 외부 이벤트를 수신하거나 병렬 작업을 시작하는 경우에는 실행 순서가 어느 정도 달라질 수 있습니다.

상태 시스템 워크플로는 상태, 전환 및 동작의 집합으로 구성됩니다. 즉, 특정 상태가 시작 상태로 표시된 다음 이벤트에 따라 다른 상태로 전환될 수 있습니다. 상태 시스템 워크플로는 또한 워크플로의 종료를 결정하는 최종 상태를 가질 수 있습니다.

새 순차적 워크플로 콘솔 응용 프로그램 프로젝트를 선택하여 만든 경우를 예로 들면, Visual Studio 2005 솔루션 탐색기에 workflow1.cs 및 workflow1.designer.cs(처음에는 숨겨짐)라는 두 파일이 포함됩니다. 이 두 파일은 만들려는 워크플로를 나타냅니다. Windows 워크플로 파운데이션 워크플로는 워크플로 모델 파일과 코드 파일 클래스로 구성됩니다. workflow1.cs 클래스는 직접 워크플로 비즈니스 논리를 작성할 수 있는 코드 파일 클래스입니다. workflow1.designer.cs 클래스는 작업 맵에 대한 설명을 나타내며 Microsoft Windows Forms 프로젝트의 폼과 마찬가지로 Visual Studio 2005에서 자동으로 관리됩니다. 워크플로에 작업을 추가하면 Visual Studio 2005에서는 프로그래밍 방식으로 작업 맵을 작성하는 Microsoft C# 코드를 사용하여 디자이너 클래스를 업데이트합니다. Windows Forms와의 유사함을 유지하기 위해 워크플로는 폼과 비슷하게, 작업은 컨트롤과 비슷하게 사용됩니다.

작업 레이아웃의 경우에도 비슷하게 XML 워크플로 태그 형식을 선택할 수 있습니다. 프로젝트에서 workflow1.cs 파일을 삭제하고 그림 2와 같이 워크플로 항목을 새로 추가하여 이러한 방식을 직접 확인할 수 있습니다.


그림 2. 코드가 분리된 순차적 워크플로 항목 추가

이제 프로젝트는 workflow1.xoml 및 workflow1.xoml.cs라는 두 파일을 포함합니다. 첫 번째 파일은 워크플로 모델을 나타내는 XML 워크플로 태그를 포함하며 두 번째 파일은 워크플로의 소스 코드와 이벤트 처리기를 포함하는 코드 파일 클래스입니다. .xoml 파일을 두 번 클릭하면 워크플로 디자이너를 직접 실행할 수 있습니다(그림 3 참조).

워크플로를 어셈블리로 컴파일한 이후에는 차이가 없으므로 워크플로 모델의 serialization에 태그와 코드 중 어느 것을 선택하더라도 런타임에는 영향을 주지 않습니다.

워크플로 응용 프로그램은 데이터 전송 및 수신 등을 실행하는 작업과 하위 작업 집합의 실행을 관리하는 IfElseWhile과 같은 복합 작업이 혼합되어 구성됩니다. 워크플로는 문서 검토, PO 승인, IT 사용자 관리, 파트너 간 정보 교환, 모든 종류의 마법사, 업무용 응용 프로그램 등 복잡한 종단 간 시나리오를 구현하는 데 사용할 수 있습니다.

그림 3은 codeActivity1 블록이라는 한 개의 작업을 포함하는 매우 간단한 워크플로 샘플을 보여 줍니다.


그림 3. Visual Studio 2005 워크플로 디자이너

코드 블록은 CodeActivity 클래스의 인스턴스에 해당하며 사용자 정의 코드로 동작이 표현되는 워크플로 내의 작업을 나타냅니다. Visual Studio 2005의 디자이너에서 선택한 요소를 두 번 클릭하면 백 엔드 코드를 입력할 수 있으며 이는 ASP.NET 응용 프로그램 및 기타 Visual Studio 2005 프로젝트와 동일한 프로그래밍 스타일입니다.

작업을 두 번 클릭하면 코드 파일이 열리고 코드 처리기의 스텁이 제공됩니다.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
  // 여기에 코드를 추가합니다.
}
(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

코드 처리기에서 입력하는 모든 문은 워크플로 런타임이 워크플로에 따라 진행되면서 지정된 작업 블록을 처리하는 지점에 이르면 실행됩니다. 다음은 환영 메시지를 출력하는 예입니다.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
  CodeActivity c = (CodeActivity)sender;
  Console.WriteLine("Hello, from '{0}'.\nI'm an instance of the {1} class.", c.Name, c.ToString());
}

시각적인 레이아웃과는 별도로 워크플로는 workflow1.xoml.cs 파일에 저장되는 다음 코드로 구성됩니다.

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace HelloWorldWorkflow
{
  public partial class Workflow1 : SequentialWorkflowActivity
  {
    private void codeActivity1_ExecuteCode(object sender, EventArgs e)
    {
      CodeActivity c = (CodeActivity)sender;
      Console.WriteLine("Hello, from '{0}'.\nI'm an instance of the {1} class.", c.Name, c.ToString());
    }
  }
}

partial 특성은 .NET Framework 2.0에 새로 소개되는 개념인 partial 클래스를 나타냅니다. partial 클래스는 클래스 정의가 여러 소스 파일에 분산될 수 있는 클래스입니다. 각 소스 파일에는 일반적인 클래스 정의 전체가 포함되어 있는 것처럼 보이지만 실제로는 partial 클래스가 포함되어 있으며 클래스에 필요한 논리가 모두 사용되지는 않습니다. 컴파일러에서는 partial 클래스 정의를 컴파일 가능한 완전한 클래스 정의로 병합합니다. partial 클래스는 개체 지향과는 관계가 없으며 프로젝트에서 클래스의 동작을 확장하기 위한 소스 수준의 방식이고 어셈블리에만 적용됩니다. .NET Framework 2.0에서는 Visual Studio 2005에서 자동으로 생성된 코드가 코드 파일 내에 삽입되는 것을 방지하는 데 partial 클래스를 사용합니다. 원본 클래스에서 누락된 바인딩 코드는 런타임 시 partial 클래스 추가를 통해 추가됩니다.

워크플로는 Windows 워크플로 파운데이션 워크플로 런타임에 의해서만 실행될 수 있으며, 워크플로 런타임에서는 외부 응용 프로그램이 몇 가지 규칙에 따라 워크플로를 호스팅해야 합니다. Visual Studio 2005에서는 테스트를 위해 program.cs 파일을 프로젝트에 추가합니다. 이 파일은 다음과 같은 간단한 콘솔 응용 프로그램입니다.

class Program
{
  static void Main(string[] args)
  {
    WorkflowRuntime workflowRuntime = new WorkflowRuntime();

    AutoResetEvent waitHandle = new AutoResetEvent(false);
    workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
    {
      waitHandle.Set();
    };
    workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
    {
      Console.WriteLine(e.Exception.Message);
      waitHandle.Set();
    };

    WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(HelloWorldWorkflow.Workflow1));
    instance.Start();

    waitHandle.WaitOne();

    // 사용자에게 표시되는 내용입니다.
    Console.WriteLine("");
    Console.WriteLine("");
    Console.WriteLine("==========================");
    Console.WriteLine("Press any key to exit.");
    Console.WriteLine("==========================");
    Console.ReadLine();
  }
}

위 코드에서 굵게 표시된 줄을 보면 알 수 있듯이 Visual Studio 2005에서는 코드를 단순화하기 위해 워크플로 클래스 이름을 콘솔 응용 프로그램에 하드 코딩합니다. 실행이 완료된 후 콘솔 응용 프로그램이 바로 끝나지 않도록 하려면 Main 메서드 끝에 Console.ReadLine 호출을 추가하면 됩니다. 이제 워크플로를 빌드하고 테스트할 준비가 끝났으므로 F5 키를 눌러 코드를 실행합니다. 코드가 정상적으로 실행되면 그림 4와 같은 출력이 표시됩니다.


그림 4. 콘솔 호스트 응용 프로그램에서 실행된 샘플 워크플로

워크플로 응용 프로그램을 디버깅하는 방법도 간단합니다. 실제로 개발자가 할 일은 중단점을 설정하는 것이 전부입니다. C# 코드로 작업하는 것과 마찬가지로 워크플로의 코드 파일 클래스 내의 어디에나 중단점을 삽입할 수 있으며 디자이너의 뷰에서 직접 추가할 수도 있습니다. 디버거를 통해 디버깅하려는 작업을 선택하고 F9 키를 눌러 그림 5와 같이 중단점을 설정합니다.


그림 5. 워크플로의 디자이너 뷰에 설정된 중단점

코드 흐름에서 중단점이 설정된 작업에 도달하면 Visual Studio 2005에서는 워크플로 디버거에 제어권을 넘겨 줍니다(그림 6 참조). 이 시점부터는 비주얼 디자이너에서 F11 키를 눌러 한 단계씩 코드 실행을 수행하고 작업을 살펴볼 수 있습니다.


그림 6. 디버깅 세션 중 워크플로 응용 프로그램

데이터 수신 및 소비
이제 인스턴스화된 워크플로에서 데이터를 수신하고 소비할 수 있도록 수정하는 방법을 살펴봅니다. 인스턴스화된 워크플로에서 데이터를 수신하는 일반적인 수단으로는 매개 변수와 이벤트가 있습니다. 매개 변수는 코드에서 작성하는 속성으로 표현되며 작업 및 워크플로 호스트 응용 프로그램에서 사용될 수 있습니다. 이벤트를 사용하려면 워크플로 모델의 특정 지점에서 실행되고 데이터를 전달하여 외부 원본 역할을 하는 사용자 지정 작업을 만들어 추가해야 합니다. 이벤트 기반 방식에 대해서는 이 기사의 뒷부분에서 설명하고 지금은 먼저 매개 변수에 대해 살펴봅니다.

다음과 같이 워크플로의 코드 숨김 파일에 두 가지 해당 속성을 만들면 간단히 워크플로에 매개 변수 두 개를 추가할 수 있습니다.

public partial class Workflow1 : SequentialWorkflowActivity
{
  private string firstName;

  public string FirstName
  {
    get { return firstName; }
    set { firstName = value; }
  }

  private string lastName;

  public string LastName
  {
    get { return lastName; }
    set { lastName = value; }
  }
}

public 속성은 코드를 깔끔하고 단순하게 유지하는 데 사용하는 간단한 프로그래밍 기법이며 매개 변수 데이터를 사용하기 위한 필수 조건은 아닙니다. 매개 변수를 public 속성으로 래핑한 경우에는 속성 이름을 자유롭게 선택할 수 있습니다. 그러나 C#에서는 매개 변수 이름의 대/소문자를 구분한다는 점에 주의해야 합니다.

이러한 매개 변수를 통해 실제로 입력 데이터를 제공하는 것은 호스트 응용 프로그램입니다. 한편 호스트에서는 워크플로가 런타임 컨테이너로 로드되어 실행되기 위한 초기화 시점에서 모든 매개 변수를 설정한다는 점을 기억해야 합니다. 이에 대한 내용을 좀 더 자세히 살펴보기 위해 Windows Forms 기반 샘플 호스트 응용 프로그램을 작성하겠습니다. 이 샘플 응용 프로그램은 사용자가 이름과 성을 입력할 수 있는 텍스트 상자(그림 7 참조) 두 개를 표시하고 사용자가 입력한 값을 워크플로 코드 처리기로 전달합니다. 전달된 매개 변수를 소비하도록 다음과 같이 코드 처리기를 다시 작성해 보겠습니다.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
  MessageBox.Show("Welcome, " + FirstName + " " + LastName);
}

이 샘플 Windows Forms 호스트 응용 프로그램의 핵심은 Start Workflow 단추에 연결된 클릭 처리기입니다.


그림 7. 워크플로 호스트 Windows Forms 응용 프로그램

폼의 코드 숨김 클래스의 전체 소스 코드는 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;

namespace WinFormTestHost
{
  public partial class Form1 : Form
  {
    private WorkflowRuntime wr;

    public Form1()
    {
      InitializeComponent();
    }

    private void btnStartWorkflow_Click(object sender, EventArgs e)
    {
      if (wr == null)
      {
        wr = new WorkflowRuntime();
        wr.StartRuntime();
      }

      Dictionary<string, object> parameters = new Dictionary<string, object>();
      parameters.Add("FirstName", txtFirstName.Text);
      parameters.Add("LastName", txtLastName.Text);

      WorkflowInstance instance = wr.CreateWorkflow(typeof(HelloWorldWorkflow.Workflow1), parameters);
      instance.Start();
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
      if (wr != null)
      {
        if (wr.IsStarted)
        {
          wr.StopRuntime();
        }
      }
    }
  }
}

매개 변수 컬렉션을 채우는 데는 문자열과 개체로 구성된 제네릭 기반 사전을 사용해야 합니다. 이 사전에서 항목의 이름은 문자열 형식인 반면 포함된 값은 개체로 구성됩니다. 워크플로 모델에 포함된 정적 매개 변수(이 예의 경우 FirstNameLastName)의 수만큼 사전에 항목을 추가합니다. 이 두 매개 변수는 UI 텍스트 상자에 입력된 내용을 가져옵니다.

마지막으로 지정된 모델의 인스턴스를 만들면 워크플로가 실행됩니다. 런타임 개체의 StartWorkflow 메서드에는 몇 가지 오버로드가 있습니다. 이 코드에 사용된 버전에서는 워크플로 형식, 입력 매개 변수 컬렉션 및 시스템에서 생성한 GUID(Globally Unique Identifier)를 사용할 수 있습니다.

각 프로세스에는 하나의 워크플로 인스턴스만 필요하며 각 AppDomain에는 두 개 이상의 인스턴스를 사용할 수 없습니다. 여기에서 선택할 수 있는 최선의 방법은 폼의 생성자에서 직접 필요한 인스턴스를 만드는 것입니다. 동일한 런타임 개체가 다양한 워크플로 인스턴스를 처리할 수 있습니다. 런타임은 인스턴스의 GUID에 따라 인스턴스를 구별하고 특정 인스턴스의 개인 데이터를 수신합니다.


그림 8. Windows Forms 응용 프로그램에서 호스팅되는 매개 변수화된 워크플로의 작동 예

여기에서는 간단히 참고만 할 수 있도록 이 개발 단계에서의 디자이너 및 워크플로 태그 코드를 살펴봅니다. 다음은 workflow1.designer.cs 소스 파일입니다.

public sealed partial class Workflow1
{
  #region Designer generated code

  /// <summary>
  /// 디자이너 지원에 필요한 메서드입니다.
  /// 이 메서드의 내용을 코드 편집기로 수정하지 마십시오.
  /// </summary>
  private void InitializeComponent()
  {
    this.CanModifyActivities = true;
    this.codeActivity1 = new System.Workflow.Activities.CodeActivity();
    //
    // codeActivity1
    //
    this.codeActivity1.Name = "codeActivity1";
    this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode);
    //
    // Workflow1
    //
    this.Activities.Add(this.codeActivity1);
    this.Name = "Workflow1";
    this.CanModifyActivities = false;
  }

  #endregion

  private CodeActivity codeActivity1;
}

다음은 해당 워크플로 태그 내용입니다.

<SequentialWorkflowActivity x:Class="HelloWorldWorkflow.Workflow1" x:Name="Workflow1"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
  <CodeActivity x:Name="codeActivity1" ExecuteCode="codeActivity1_ExecuteCode" />
</SequentialWorkflowActivity>

속성을 매개 변수로 사용할 수 있으며 종속성 속성을 사용할 수도 있습니다. 이에 관해서는 사용자 지정 작업을 만들 때 살펴보도록 하겠습니다.

워크플로 런타임
호스트는 WorkflowRuntime 클래스를 통해 Windows 워크플로 파운데이션과 상호 작용합니다. 앞서 살펴본 샘플 호스트의 단순함 때문에 이러한 핵심을 간과해서는 안 됩니다. 호스트에서 프로세스 및 AppDomains를 한 개 이상 만들거나 필요에 따라 AppDomains 간 호출을 마샬링하거나 격리 메커니즘을 설정하는 등의 다양한 주요 작업을 수행할 수 있습니다. 확장성을 높이기 위해 시스템에 설치된 여러 CPU를 활용하거나 시스템 팜에서 여러 워크플로 인스턴스를 실행하려면 호스트에서 여러 프로세스를 만들어야 할 수도 있습니다.

그 외에도 호스트에서 다양한 작업을 수행할 수 있습니다. 예를 들어 워크플로가 오래 대기해야 하는 경우, 특정 이벤트를 수신하고 사용자 또는 관리자에게 알리는 경우, 각 워크플로의 시간 제한과 재시도 횟수를 설정하는 경우, 성능 카운터를 표시하는 경우, 그리고 디버깅 및 진단용으로 로그 정보를 기록하는 경우 등에 적용되는 정책을 제어할 수 있습니다.

호스트는 시작 시에 컨테이너에 등록된 미리 정의된 서비스 및 사용자 지정 서비스를 통해 대부분의 추가 작업을 수행합니다. 앞서 살펴본 샘플 호스트에서는 이러한 추가 작업을 수행하지 않으며 워크플로 인스턴스를 시작하는 작업만 수행합니다. 대부분의 경우 호스트는 이러한 작업을 수행하게 됩니다.

워크플로 및 작업
워크플로 프로젝트가 활성화되어 있을 때 표시되는 Visual Studio 2005 도구 상자를 다시 살펴보도록 하겠습니다. 그림 9에 나와 있는 이 도구 상자에는 단계의 순서와 각 단계의 상호 관계를 디자인하여 워크플로 모델을 구성하는 데 사용할 수 있는 작업이 나열되어 있습니다.


그림 9. Windows 워크플로 파운데이션 순차적 워크플로의 구성 요소

표 2에서는 대부분의 기본 작업에 대해 간단히 소개하고 이를 유용하게 사용할 수 있는 몇 가지 시나리오를 보여 줍니다.

표 2. Windows 워크플로 파운데이션 구성 요소
작업 설명
Code 워크플로에 Microsoft Visual Basic .NET 또는 C# 코드를 추가하여 사용자 지정 동작을 수행할 수 있도록 합니다. 그러나 추가되는 코드가 웹 서비스와 같은 외부 원본에 대한 종속성으로 워크플로를 차단해서는 안 됩니다.
Compensate 오류가 발생한 경우 워크플로에서 이미 수행된 작업을 되돌리거나 보완하는 코드를 호출할 수 있도록 합니다. 일반적으로 이전에 작업 성공에 대한 알림을 받은 사용자에게 다시 취소되었음을 알리는 전자 메일 메시지를 전송할 수 있습니다.
ConditionedActivityGroup(CAG) 워크플로에서 각 작업별 기준에 따라 CAG 완료 조건이 완전히 충족될 때까지 일련의 하위 작업의 집합을 조건부로 실행할 수 있도록 합니다. 하위 작업은 서로 독립적이며 병렬로 실행될 수 있습니다.
Delay 워크플로의 타이밍을 제어하고 지연을 설정할 수 있도록 합니다. Delay 작업에 제한 시간을 제공하여 워크플로를 일시 중지하고 다시 실행할 수 있습니다.
EventDriven 이벤트에 의해 실행되는 일련의 작업을 나타냅니다. 첫 번째 하위 작업은 위부 이벤트를 위해 대기할 수 있어야 하므로 첫 번째 하위 작업으로 사용할 수 있는 작업에는 EventSink 및 Delay가 있습니다. 이 경우 Delay는 제한 시간으로 사용됩니다.
HandleExternalEvent 서비스에서 지정된 이벤트가 발생한 경우 WorkflowRuntime에 등록된 데이터 교환 서비스로부터 워크플로가 데이터를 수신할 수 있도록 합니다.
FaultHandler 지정한 유형의 예외를 처리할 수 있도록 합니다. FaultHandler 작업은 지정한 예외가 발생했을 때 필요한 작업을 실제로 수행하는 다른 작업의 래퍼입니다. 원하는 경우 로컬 변수로 예외를 저장하고 코드 숨김으로 사용할 수 있습니다.
IfElse 워크플로에서 몇 가지 대체 분기 중 하나를 조건부로 실행할 수 있도록 합니다. 이 경우 각 분기에 대한 조건을 설정해야 하며 조건이 true인 첫 번째 분기가 실행됩니다. 마지막 분기는 "else" 분기로 처리되므로 조건을 설정할 필요가 없습니다.
CallExternalMethod 인터페이스의 메서드를 호출하여 워크플로로부터 WorkflowRuntime에 등록된 데이터 교환 서비스로 메시지를 보낼 수 있도록 합니다.
InvokeWebService 워크플로에서 웹 서비스 메서드를 호출할 수 있도록 합니다. 이 경우 WSDL를 통해 사용할 프록시 클래스와 호출할 메서드의 이름을 지정해야 합니다. 동기 호출과 비동기 호출이 모두 지원됩니다.
InvokeWorkflow 워크플로에서 원하는 수준까지 다른 워크플로를 호출하거나 시작할 수 있도록 합니다. 예를 들어 호출된 워크플로가 세 번째 워크플로를 호출하고 다시 네 번째 워크플로를 호출하는 방식으로 계속 워크플로를 호출할 수 있습니다. 재귀 호출은 지원되지 않으며 지원되는 호출 모델은 실행 후 값을 추적하지 않는(fire-and-forget) 방식입니다.
Listen 워크플로에서 몇 가지 이벤트가 발생할 때까지 대기하거나 지정한 제한 시간이 경과한 후에 대기를 중단하거나 결과에 따라 분기할 수 있도록 합니다. 각 분기에 이벤트 기반 작업을 한 개 이상 추가할 수 있습니다. 이 경우 조건에 맞는 첫 번째 분기만 실행되며 다른 분기는 실행되지 않습니다.
Parallel 워크플로에서 둘 이상의 작업을 서로 독립적으로 수행할 수 있도록 합니다. 이 경우 두 해당 작업이 종료된 후에 워크플로 작업이 계속 실행됩니다.
Policy 사용자가 규칙 컬렉션을 표시하고 계산할 수 있도록 합니다. 각 규칙에는 true일 경우 실행할 동작을 지정할 수 있습니다.
Replicator 워크플로에서 특정 작업의 인스턴스를 임의의 개수만큼 만들고 순차적으로 또는 동시에 실행할 수 있도록 합니다.
Sequence 하위 작업 집합의 연속 실행을 조정할 수 있도록 합니다. 마지막 하위 작업이 완료되면 시퀀스가 종료됩니다.
SetState 상태 시스템 워크플로에서 새 상태로의 전환을 지정할 수 있도록 합니다.
State 상태 시스템 워크플로의 상태를 나타냅니다.
StateInitialization State 작업 내에서 상태가 전환될 때 실행되는 하위 작업의 컨테이너로 사용됩니다.
StateFinalization State 작업 내에서 상태가 종료될 때 실행되는 하위 작업의 컨테이너로 사용됩니다.
Suspend 오류가 발생했을 때 개입할 수 있도록 워크플로의 동작을 일시 중단합니다. 워크플로 인스턴스가 일시 중단되면 로그에 오류가 기록됩니다. 관리자가 문제를 진단하는 데 도움이 되는 메시지 문자열도 지정할 수 있습니다. 현재 인스턴스와 관련된 모든 상태 정보가 저장되며 관리자가 실행을 다시 시작하면 복구됩니다.
Terminate 비정상적인 상황이 발생했을 때 워크플로 동작을 즉시 종료할 수 있도록 합니다. Parallel 작업 내에서 호출된 경우 현재 상태에 관계없이 모든 분기가 즉시 종료됩니다. 워크플로가 종료되면 관리자가 문제를 진단하는 데 도움이 되는 메시지와 오류가 로그에 기록됩니다.
Throw 지정한 유형의 예외를 일으킬 수 있도록 합니다. 이 작업을 사용하는 것은 사용자 코드에서 예외를 일으키는 코드 처리기를 사용하는 것과 같습니다. 이 작업은 .NET 예외를 일으키는 선언적인 방법입니다.
TransactionalScope transactional scope는 작업을 그룹화하는 블록입니다. 이 작업은 주로 트랜잭션 실행, 보완 및 예외 처리에 사용됩니다.
SynchronizationScope 이 작업을 사용하여 작업 내에서 공유 데이터에 대한 모든 액세스를 올바르게 serialize할 수 있습니다.
WebServiceInput 워크플로가 웹 서비스로 노출되어 웹 서비스 요청을 직접 수신할 수 있도록 합니다.
WebServiceOutput 워크플로가 웹 서비스로 노출되어 웹 서비스 요청에 직접 응답할 수 있도록 합니다.
WebServiceFault 이 작업은 ASMX 웹 서비스 프레임워크 메서드에서 예외가 일어난 경우에 해당하는 웹 서비스 실패 시에 대비하는 모델을 제공합니다.
While 워크플로에서 조건이 충족되는 하나 이상의 작업을 실행할 수 있도록 합니다. 조건은 각 반복이 실행되기 전에 계산됩니다. true일 경우 하위 작업이 실행되고 그렇지 않은 경우 작업이 완료됩니다. 선언적인 조건이나 코드 조건을 지정할 수 있습니다.

작업은 Windows 워크플로 파운데이션을 사용한 워크플로 프로그래밍의 선언적인 방식을 나타냅니다. 개발자는 디자인 시 작업을 사용하여 워크플로 모델을 구성하고 각 작업의 속성에 값을 할당합니다. 코드가 분리된 워크플로 항목을 선택한 경우 최종 결과는 확장명이 .xoml인 워크플로 태그 파일에 XML 태그로 저장됩니다. 그렇지 않으면 개발자가 구성한 모델은 워크플로 개체 모델에 대한 호출 순서에 따라 디자이너에서 생성한 C# 또는 Visual Basic .NET 클래스 파일에 유지됩니다. 전자는 ASP.NET 페이지와 비슷한 방식이며 후자는 Windows Forms 응용 프로그램의 방식과 비슷합니다.

Visual Studio 2005에서는 두 방식의 차이점이 거의 나타나지 않습니다. 개발자는 항상 시각적으로 워크플로를 디자인하게 되고 이를 별개의 두 형식으로 유지하는 작업은 Visual Studio 2005에서 투명하게 처리됩니다. XOML 및 코드 분리를 사용하지 않고 코드 전용 솔루션을 선택한 경우에는 디자이너 코드를 조작하여 보다 유연하게 만들 수 있습니다. 예를 들어 구성 파일이나 데이터베이스에서 매개 변수의 기본값을 읽도록 코드를 작성할 수 있습니다. 워크플로 태그와 코드 분리를 사용하는 경우에는 워크플로 코드와 해당 모델이 깔끔하게 분리됩니다.

워크플로 모델을 프로그래밍 방식으로 수정할 수 있을까요? 디자인 시에는 Visual Studio에서 수행할 수 있는 모든 워크플로 작업을 프로그래밍 방식으로 처리할 수 있습니다. 런타임 시에는 작업 컬렉션을 변경할 수 있으며 실행 중인 워크플로 인스턴스도 변경할 수 있습니다. 워크플로 변경은 디자인 시에는 알 수 없었던 비즈니스 변경 사항이 있거나 비즈니스 프로세스를 수정하고 완료하는 비즈니스 논리가 필요할 경우에 이루어집니다. 어떤 경우에도 완전히 다시 디자인하는 것이 아니라 보완하는 정도의 제한된 변경만 이루어져야 합니다.

워크플로 변경 내용은 응용 프로그램 컨텍스트에서 하나의 워크플로 인스턴스에만 적용되며 동일한 워크플로 유형의 이후 인스턴스에는 변경 내용이 적용되지 않습니다. 워크플로 인스턴스 자체에서 내부적으로 변경하는 것이 가능하며 응용 프로그램 코드에서 외부적으로도 변경할 수 있습니다.

Windows 워크플로 파운데이션 프레임워크에서는 워크플로를 ASP.NET 클라이언트 및 다른 워크플로에 웹 서비스로 노출하는 기능을 비롯하여 웹 서비스 상호 운용성을 지원합니다. 또한 Windows 워크플로 파운데이션에서는 Microsoft IIS 6.0에서 ASP.NET을 실행하는 서버 팜 또는 웹 서버에 ASP.NET 웹 서비스로 워크플로를 게시하는 기능을 지원합니다.

Windows 워크플로 파운데이션 프레임워크 작업 집합에는 워크플로를 웹 서비스 끝점으로 사용할 수 있도록 하는 WebServiceReceive 작업과 WebServiceResponse 작업이 포함됩니다.

워크플로를 웹 서비스로 노출하려면 클라이언트로부터의 호출을 수신하는 WebServiceReceive 작업이 워크플로에 포함되어야 합니다. 그림 10에 표시된 가기 메뉴 명령을 선택하면 워크플로를 웹 서비스로 게시할 수 있습니다.


그림 10. 워크플로를 웹 서비스로 게시

사용자 지정 작업 개발
Windows 워크플로 파운데이션의 확장성에서 핵심은 사용자 지정 작업을 작성함으로써 워크플로 모델을 빌드하는 데 사용할 수 있는 구성 요소 집합을 확장할 수 있다는 것입니다.

전자 메일 메시지를 보내는 작업을 개발하는 과정을 통해 내부 아키텍처를 살펴보도록 하겠습니다. Windows 워크플로 파운데이션에서는 사용자 지정 작업을 만드는 데 바로 사용할 수 있는 Visual Studio 2005 템플릿을 제공합니다. 워크플로 작업 라이브러리라고 하는 이 템플릿은 필요에 따라 이름을 바꿀 수 있는(예: SendMailActivity) C# 파일을 만듭니다. 작업은 부모 클래스에서 상속되는 일반 클래스입니다. 기존 작업이나 기본 제공 작업 또는 직접 만든 작업이나 타사 공급업체에서 제공한 작업을 기반으로 새 작업을 만들 수 있습니다. 새 구성 요소에는 부모 클래스로부터 상속된 미리 정의된 동작이 추가됩니다. 작업을 완전히 새로 만들려면 Activity에서 파생하도록 선택합니다. 다음 코드 샘플에서는 새 클래스의 기본 구조를 보여 줍니다.


public partial class SendMailActivity : System.Workflow.ComponentModel.Activity
{
  public SendMailActivity()
  {
    InitializeComponent();
  }

  protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
  {
    return base.Execute(executionContext);
  }
}


쉽게 알 수 있듯이 Execute 메서드는 구성 요소의 핵심입니다. 즉, 이 메서드에서 구성 요소의 핵심 작업이 수행됩니다.

개발된 작업은 도구 상자에 배치되며 새 워크플로 응용 프로그램에 끌어서 놓을 수 있습니다. 속성 목록은 필수는 아니지만 속성 없이 유용한 작업을 만들기는 어렵습니다. 사용자 지정 작업에는 기본 속성보다 종속성 속성을 사용하는 것이 유용한 경우가 많습니다. 종속성 속성을 사용하면 속성 값을 해당 사용자 지정 작업을 사용하는 워크플로 내 다른 작업의 다른 속성을 포함한 관련 데이터에 바인딩할 수 있습니다. 그러면 SendMailActivity에서 사용할 첫 번째 종속성 속성을 정의하겠습니다. 그림 11과 같이 Windows 워크플로 파운데이션과 함께 제공되는 코드 조각을 사용하면 종속성 속성을 간편하게 삽입할 수 있습니다.


그림 11. 코드 조각 삽입

이 코드 조각을 삽입하면 DependencyProperty의 정적 인스턴스와 함께 속성의 런타임 값을 검색하는 키로 DependencyProperty를 사용하는 래핑 멤버 속성이 생성됩니다. 이것은 작업의 각 인스턴스에 정적 DependencyProperty 개체를 키로 사용하여 액세스할 수 있는 속성 값의 해시 테이블이 있는 것과 같습니다. 아래 그림 12에서와 같이 코드 조각 사이를 이동하면서 새 속성을 사용자 지정하여 전자 메일을 보낸 사람을 표시하도록 할 수 있습니다.


그림 12. "From" 매개 변수 사용자 지정

속성의 런타임 값은 사용자 지정 작업의 기본 클래스에 의해 제어되므로 사용자 지정 작업을 디자인하고 빌드한 후에는 선언적으로 다른 속성에 바인딩할 수 있습니다. 여기에서는 보낼 전자 메일 메시지를 완벽하게 구성할 수 있도록 To, Subject, BodyHost에 대한 종속성 속성을 만들어 보겠습니다.

마지막 단계는 작업이 실행되면 전자 메일 메시지를 보내도록 Execute 메서드를 약간 수정하는 것입니다.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
  MailAddress toAddress = new MailAddress(To);
  MailAddress fromAddress = new MailAddress(From);

  MailAddressCollection addresses = new MailAddressCollection();
  addresses.Add(toAddress);

  MailMessage msg = new MailMessage(fromAddress, toAddress);
  msg.Subject = Subject;
  msg.Body = Body;

  SmtpClient mail = new SmtpClient(Host);
  mail.Send(msg);
  return ActivityExecutionStatus.Closed;
}

워크플로 솔루션 내에서 작업 프로젝트를 개발한 경우 워크플로 문서에서는 도구 상자에 나열된 새 작업이 자동으로 검색됩니다(그림 13 참조). 그렇지 않으면 도구 상자를 마우스 오른쪽 단추로 클릭하여 작업을 수동으로 추가해야 합니다.


그림 13. 도구 모음에 표시된 SendMail 작업

이제 워크플로에 SendMail 작업을 사용하여 종속성 속성의 몇 가지 기능을 직접 확인해볼 수 있습니다. 예를 들어 워크플로에 SendMail 작업을 추가하고 Body 속성에 대해 <Promote...>를 선택하면 앞에서 살펴본 FirstName 및 LastName의 경우와 마찬가지로 워크플로의 새로운 매개 변수에 전자 메일 본문을 전달할 수 있습니다. 이것은 종속성 속성을 사용하여 워크플로 동작을 중앙 집중식으로 제어하는 방법을 보여 주는 한 예입니다.

그림 14에서는 SendMail 작업의 실제 작동을 보여 줍니다.


그림 14. SendMail 작업 실행

보다 현실적인 워크플로 계획
표 2에 나열되어 있는 작업 중 일부를 결합하여 보다 현실적인 작업을 해결하는 방법을 살펴보겠습니다. 주문이 완료되기까지 몇 가지 상태로 전환되는 비즈니스 응용 프로그램을 예로 들면, 일반적인 시나리오에서는 주문의 현재 상태에 따라 어떤 이벤트가 발생할 수 있는지를 나타내는 규칙이 있습니다. 예를 들어 진행 중인 주문을 처리 또는 업데이트할 수는 있지만 취소 또는 납품할 수는 없습니다.

이벤트가 발생하면 상태 시스템 워크플로에서 주문의 상태를 전환합니다. 예를 들어 주문이 진행 중인 동안 BeingProcessed 이벤트가 발생하면 상태 시스템 워크플로에서는 주문을 적절한 상태로 전환합니다. 그림 15에서는 샘플 주문 상태 시스템 워크플로의 다이어그램을 보여 줍니다.


그림 15. 주문을 관리하는 상태 시스템의 샘플 스키마

먼저 상태 시스템 워크플로를 만들어 보겠습니다. State 작업을 사용하여 주문에 대해 사용할 수 있는 상태를 모델링하고 EventDriven 작업을 사용하여 각 상태에서 발생할 수 있는 이벤트를 지정합니다. 사용자 지정 서비스를 통해 외부 이벤트가 발생하면 주문의 상태가 전환됩니다. 전환을 수행하려면 SetState 작업을 사용합니다. 워크플로를 만든 후에는 Windows Forms 호스트 응용 프로그램을 사용하여 실행합니다.

워크플로는 특정 용도로 설정된 서비스를 통해 외부와 통신합니다. 이러한 서비스는 워크플로 내에 있는 이벤트 기반 작업과 관련이 있는 이벤트를 생성합니다. 마찬가지로 해당 서비스는 워크플로에서 호스트를 호출하여 데이터를 전송하기 위한 public 메서드를 노출합니다. 메서드와 이벤트는 인터페이스에서 정의되며 이러한 인터페이스를 데이터 교환 서비스라고도 합니다. 이 서비스는 입력 및 출력에 관계없이 워크플로가 외부 구성 요소와 상호 작용할 때마다 필요합니다.

데이터 교환 서비스는 인터페이스 정의와 이러한 인터페이스를 구현하는 클래스를 최소한 한 개씩 포함하는 일반적인 .NET 클래스 라이브러리입니다. 이 인터페이스는 나타내려는 작업에 맞게 구성됩니다. 이 예에서 상태 시스템은 주문의 처리 과정을 나타내며 인터페이스는 다음 5가지 이벤트로 구성됩니다.

[ExternalDataExchange]
public interface IOrderService
{
  event EventHandler<OrderEventArgs> OrderCreated;
  event EventHandler<OrderEventArgs> OrderShipped;
  event EventHandler<OrderEventArgs> OrderUpdated;
  event EventHandler<OrderEventArgs> OrderProcessed;
  event EventHandler<OrderEventArgs> OrderCanceled;
}

[ExternalDataExchange] 특성은 IOrderService를 데이터 교환 서비스 인터페이스로 표시하여 워크플로 인스턴스와의 데이터 교환에 사용될 것임을 워크플로 런타임에 알립니다. 이 예의 경우 호스트는 여러 EventDriven 작업에 이벤트를 생성하여 워크플로 인스턴스에 데이터를 보냅니다. 필요한 경우 워크플로 인스턴스 내에서 CallExternalMethod 작업을 통해 IOrderService 인터페이스의 메서드를 호출할 수 있습니다.

인터페이스의 이벤트 선언에는 .NET Framework 2.0의 새로운 기능인 제네릭이 사용됩니다. EventHandler 클래스는 이벤트를 처리하는 데 사용되는 함수의 프로토타입을 나타내는 대리자입니다. .NET Framework 1.x에서 EventHandler는 다음과 같이 정의됩니다.

void EventHandler(object sender, EventArgs e)

이벤트가 OrderEventArgs와 같은 사용자 지정 데이터 구조를 전달할 수 있도록 하려면 새 대리자를 만들고 EventHandler 대신 사용해야 합니다. 예를 들면 다음과 같습니다.

delegate void OrderEventHandler(object sender, OrderEventArgs e)

.NET Framework 2.0에서도 이러한 방식을 그대로 사용할 수 있지만 .NET Framework 2.0의 제네릭을 사용하면 새 대리자 클래스를 명시적으로 정의 및 인스턴스화하지 않고도 동일한 결과를 얻을 수 있습니다. 즉, EventHandler<T> 대리자의 제네릭 버전을 사용하면 되며, 여기에서 이벤트 데이터의 형식은 매개 변수입니다.

Windows 워크플로 파운데이션 ExternalDataEventArgs 클래스에서 파생된 사용자 지정 클래스인 OrderEventArgs 형식의 데이터를 클라이언트에 전달하는 이벤트를 다음과 같이 동일한 어셈블리 내에서 정의하였습니다.

[Serializable]
public class OrderEventArgs : ExternalDataEventArgs
{
  private string _orderId;

  public OrderEventArgs(Guid instanceId, string orderId) : base(instanceId)
  {
    _orderId = orderId;
  }

  public string OrderId
  {
    get { return _orderId; }
    set { _orderId = value; }
  }
}

다음은 인터페이스를 구현하는 클래스를 정의합니다. 이 클래스에는 인터페이스에서 발생할 수 있는 이벤트 개수만큼의 public 메서드가 포함됩니다.

public class OrderService : IOrderService
{
  public OrderService()
  {
  }

  public void RaiseOrderCreatedEvent(string orderId, Guid instanceId)
  {
    if (OrderCreated != null)
      OrderCreated(null, new OrderEventArgs(instanceId, orderId));
  }

  public void RaiseOrderShippedEvent(string orderId, Guid instanceId)
  {
    if (OrderShipped != null)
      OrderShipped(null, new OrderEventArgs(instanceId, orderId));
  }

  public void RaiseOrderUpdatedEvent(string orderId, Guid instanceId)
  {
    if (OrderUpdated != null)
      OrderUpdated(null, new OrderEventArgs(instanceId, orderId));
  }

  public void RaiseOrderProcessedEvent(string orderId, Guid instanceId)
  {
    if (OrderProcessed != null)
      OrderProcessed(null, new OrderEventArgs(instanceId, orderId));
  }

  public void RaiseOrderCanceledEvent(string orderId, Guid instanceId)
  {
    if (OrderCanceled != null)
      OrderCanceled(null, new OrderEventArgs(instanceId, orderId));
  }

  public event EventHandler>OrderEventArgs< OrderCreated;
  public event EventHandler>OrderEventArgs< OrderShipped;
  public event EventHandler>OrderEventArgs< OrderUpdated;
  public event EventHandler>OrderEventArgs< OrderProcessed;
  public event EventHandler>OrderEventArgs< OrderCanceled;
}

이제 주문 서비스가 있는 어셈블리를 컴파일하고 상태 시스템 워크플로 프로젝트로 다시 전환합니다. 워크플로 프로젝트에서 먼저 새로 만든 어셈블리에 대한 참조를 추가합니다. 그런 다음 4개의 State 작업을 추가하고 이름을 WaitingForOrderState, OrderOpenState, OrderProcessedState, OrderCompletedState로 지정합니다.

표 3에서는 워크플로의 상태 다이어그램을 나타냅니다. 각 상태별로 다른 상태로의 전환을 유발할 수 있는 몇 가지 이벤트가 있습니다.

표 3. 주문을 위한 샘플 상태 시스템
상태 지원되는 이벤트 다음 상태로 전환
WaitingForOrderState OrderCreated OrderOpenState
OrderOpenState OrderUpdated OrderOpenState
  OrderProcessed OrderProcessedState
OrderProcessedState OrderUpdated OrderOpenState
  OrderCanceled 작업 종료
  OrderShipped OrderCompletedState
OrderCompletedState    

다이어그램을 구현하려면 이 표에서 지원되는 것으로 표시된 이벤트 개수만큼의 EventDriven 블록을 각 State 작업에 모두 추가합니다. 예를 들어 WaitingForOrderState라는 State 작업에는 OrderCreatedEvent(임의의 이름)라는 EventDriven 작업이 하나 포함됩니다. EventDriven 작업은 그림 16과 같이 외부 이벤트를 캡처하고 새 상태로 전환하기 위해 HandleExternalEvent 작업과 SetState 작업을 포함합니다.


그림 16. OrderCreatedEvent EventDriven 작업의 내부 보기

HandleExternalEvent 작업의 속성 창에서 데이터 교환 서비스(이 예의 경우 IOrderService 인터페이스)와 구독할 이벤트 이름을 선택합니다. HandleExternalEvent 작업의 속성 창에서 InterfaceType 항목을 클릭하면 Visual Studio 2005에서 프로젝트에 사용할 수 있는 데이터 교환 서비스의 목록이 표시됩니다. 여기에서 서비스를 선택하면 EventName 속성에 해당 서비스에서 노출하는 이벤트 목록이 반영됩니다. 원하는 이벤트를 선택하고 진행합니다. OrderCreatedEvent 작업의 경우 OrderCreated 이벤트를 선택합니다.

SetState 작업은 TargetStateName 속성에 따라 표시되는 새로운 상태로 시스템을 전환합니다. 그림 16의 SetState 작업은 OrderOpenState로 설정됩니다.

표 3에 있는 모든 상태와 이벤트 싱크에 대해 위의 작업을 반복하면 그림 17과 같은 워크플로가 완성됩니다.


그림 17. 완성된 주문 상태 시스템

마지막 단계로 워크플로를 테스트할 Windows Forms 응용 프로그램을 작성해야 합니다. 이 응용 프로그램의 사용자 인터페이스에는 보류된 모든 주문을 추적할 수 있는 목록 보기와 새 주문을 만드는 데 사용하는 텍스트 상자 및 단추가 포함되며 주문을 업데이트, 처리 및 종료하는 다른 단추도 사용됩니다.

상태 시스템 워크플로는 Form_Load 이벤트에서 초기화됩니다. 상태 시스템 워크플로의 초기화는 특히 상태 변경 내용을 추적하려는 경우에 순차적 워크플로보다 다소 복잡해집니다. 다음 코드 샘플에서는 워크플로 런타임을 초기화하는 방법을 보여 줍니다.

private void StartWorkflowRuntime()
{
  // 이 응용 프로그램에 사용할 새 워크플로 런타임을 만듭니다.
  _runtime = new WorkflowRuntime();

  // WorkflowRuntime 개체의 이벤트 처리기를 등록합니다.
  _runtime.WorkflowTerminated += new
    EventHandler<WorkflowTerminatedEventArgs&gy(WorkflowRuntime_WorkflowTerminated);
  _runtime.WorkflowCompleted += new
    EventHandler<WorkflowCompletedEventArgs>(WorkflowRuntime_WorkflowCompleted);

  // StateMachineTrackingService 클래스의 새 인스턴스를 만듭니다.
  _stateMachineTrackingService = new StateMachineTrackingService(_runtime);

  // OrderService의 호스트 서비스를 만들어 런타임에 추가합니다.
  ExternalDataExchangeService dataService = new ExternalDataExchangeService();
  _runtime.AddService(dataService);

  // OrderService의 새 인스턴스를 호스트 서비스에 추가합니다.
  _orderService = new OrderService();
  _runtime.AddService(_orderService);
}

StateMachineTrackingService는 런타임 위에서 작동하여 워크플로 내의 상태 변경 사항을 추적할 수 있도록 해 줍니다. 데이터 교환 서비스의 인스턴스도 런타임에 추가됩니다.

사용자가 새 주문을 만드는 단추를 클릭하면 다음 코드가 실행됩니다.

private Guid StartOrderWorkflow(string orderID)
{
  // WorkflowInstanceId의 새 GUID를 만듭니다.
  Guid instanceID = Guid.NewGuid();

  // OrderWorkflows 어셈블리를 로드합니다.
  Assembly asm = Assembly.Load("OrderWorkflows");

  // OrderWorkflows.Workflow1 클래스에 대한 형식 참조를 가져옵니다.
  Type workflowType = asm.GetType("OrderWorkflows.Workflow1");

  // 상태 추적을 지원하는 상태 시스템의 인스턴스를 새로 시작합니다.
  StateMachineInstance stateMachine =
    _stateMachineTrackingService.RegisterInstance(workflowType, instanceID);
  stateMachine.StateChanged += new
    EventHandler<ActivityEventArgs>(StateMachine_StateChanged);
  stateMachine.StartWorkflow();
  _stateMachineInstances.Add(instanceID.ToString(), stateMachine);

  // 워크플로 GUID를 반환합니다.
  return instanceID;
}

이 코드는 먼저 워크플로 인스턴스를 인스턴스화하고 상태 변경에 대한 이벤트 처리기를 등록합니다. 형식 정보를 가져오기 위해 반드시 .NET 리플렉션을 사용할 필요는 없지만 유연성을 크게 높일 수는 있습니다. 워크플로 인스턴스의 형식을 워크플로 런타임에 전달하는 데는 기존의 typeof 연산자로 충분합니다.

그림 18은 실행 중인 샘플 응용 프로그램을 보여 줍니다. 단추는 선택한 워크플로 인스턴스의 상태에 따라 활성화됩니다.


그림 18. Windows Forms 응용 프로그램에서 호스팅되는 상태 시스템 워크플로

사용자가 지정된 단추를 클릭하면 통신 인터페이스에서 해당 이벤트가 발생하고 워크플로의 이벤트 싱크에 의해 포착됩니다. 예를 들어 진행 상태인 워크플로 인스턴스에 대해 Order Processed 단추를 클릭하면 다음과 같이 처리됩니다.

private void btnOrderEvent_Click(object sender, EventArgs e)
{
  // 클릭한 단추의 이름을 가져옵니다.
  string buttonName = ((Button)sender).Name;

  // 선택한 주문의 GUID를 가져옵니다.
  Guid instanceID = GetSelectedWorkflowInstanceID();

  // 선택한 주문의 ID를 가져옵니다.
  string orderID = GetSelectedOrderID();

  // 계속하기 전에 단추를 비활성화합니다.
  DisableButtons();

  // 클릭한 단추의 이름에 따라 실행할 작업을 결정합니다.
  switch(buttonName)
  {
    // Order Local Service를 사용하여 OrderShipped 이벤트를 발생시킵니다.
    case "btnOrderShipped":
       _orderService.RaiseOrderShippedEvent(orderID, instanceID);
       break;

    // Order Local Service를 사용하여 OrderUpdated 이벤트를 발생시킵니다.
    case "btnOrderUpdated":
       _orderService.RaiseOrderUpdatedEvent(orderID, instanceID);
       break;

    // Order Local Service를 사용하여 OrderCanceled 이벤트를 발생시킵니다.
    case "btnOrderCanceled":
       _orderService.RaiseOrderCanceledEvent(orderID, instanceID);
       break;

    // Order Local Service를 사용하여 OrderProcessed 이벤트를 발생시킵니다.
    case "btnOrderProcessed":
       _orderService.RaiseOrderProcessedEvent(orderID, instanceID);
       break;
  }
}

워크플로에서 발생한 이벤트는 그림 19와 같이 EventDriven 작업에 의해 포착됩니다.


그림 19. OrderProcessed 이벤트를 처리하는 상태 시스템의 EventDriven 블록

HandleExternalEvent 작업은 이벤트를 캡처하고 이를 SetState 작업에 따라 설정된 상태에서 전환하여 처리합니다. 앞서 목록에서 살펴본 대로 워크플로 내의 상태 변경은 추가 상태 추적 서비스에서 감지되고 StateChanged 이벤트를 통해 호스트에 보고됩니다.

이 문서에서 설명한 모든 예의 전체 소스 코드와 기타 워크플로 관련 콘텐츠는 http://msdn.microsoft.com/workflow (영문)에서 확인할 수 있습니다.

결론
새 Microsoft 제품 또는 기존 Microsoft 제품의 워크플로 프레임워크로 사용하도록 설계된 Windows 워크플로 파운데이션은 .NET 플랫폼용 워크플로 기반 응용 프로그램을 개발해야 하는 모든 개발자에게 WinFX의 강력한 성능과 Visual Studio 2005의 사용 편의성을 동시에 제공합니다.

Windows 워크플로 파운데이션의 가장 큰 이점으로는 통합된 워크플로 모델과 함께 다수의 소유 라이브러리를 대체할 수 있는 도구 집합을 들 수 있습니다. 또한 Windows 워크플로 파운데이션을 사용할 경우 하위 수준의 코드를 유지 관리할 필요가 없고 상위 수준의 작업에 집중할 수 있으므로 워크플로 제품을 제공하는 공급업체에도 Windows 워크플로 파운데이션은 매우 중요한 의미가 있습니다.

Windows 워크플로 파운데이션은 응용 프로그램의 종류에 관계없이 다양한 요구 사항을 해결하도록 설계된 워크플로 기술입니다. 즉, Windows 워크플로 파운데이션은 모든 수준에서의 확장성을 고려하여 설계된 광범위한 프레임워크입니다. 이러한 확장성을 잘 보여 주는 예로는 사용자 지정 작업과 연결 가능한 런타임 서비스를 들 수 있습니다. 사용자 지정 작업을 사용하면 워크플로를 작성하는 데 사용할 수 있는 구성 요소를 확장할 수 있습니다. 지속성 및 추적과 같은 런타임 서비스는 응용 프로그램 환경에 맞게, 또는 Microsoft SQL Server나 다른 공급업체의 데이터베이스에 저장이 가능하도록 변경할 수 있습니다.

Visual Studio 2005의 Windows 워크플로 파운데이션 확장 기능은 직접적인 코드 액세스뿐 아니라 워크플로의 시각적 모델링을 가능하게 해 줍니다.

또한 비주얼 디자이너를 다른 디자인 환경에서 호스팅할 수 있으므로 디자이너 공급자는 시각적 모델링 기능을 자신의 환경에 추가하고 응용 프로그램 사용자에게 익숙한 사용자 환경을 제공할 수 있습니다.

이 기사에서는 Windows 워크플로 파운데이션 기술과 기능에 대한 기초적인 내용을 설명하고 작동 방식, 내부 구조 및 몇 가지 대표적인 샘플 코드를 소개하였습니다.

출처 : 한국 마이크로소프트 MSDN (2006년 2월)