Category Archives: WWF

Windows Workflow Foundation – persystencja stanu

Czasami istnieje potrzeba zapisania stanu aktualnie wykonywanego workflow’a. Domyślnie wszelkie dane zapisywane są w pamięci ulotnej. W przypadku awarii komputera odtworzenie ostatnio wykonywanego stanu jest niemożliwe. Nie jest to problem w przypadku gdy wykonanie workflow’a zajmuje tylko kilka sekund. W sytuacji w której zakończenie workflow’a może potrwać kilka godzin lub tygodni, niezbędne jest zapisywanie informacji w pamięci trwałej np. w bazie danych SQL Server.

WWF dostarcza specjalną usługę, SqlWorkflowPersistenceService, która większość brudnej roboty wykona za nas. Dzięki usłudze nie musimy kłopotać się z ręcznym projektowaniem struktury bazy danych i zapisem stanu. Wszystko wykona za nas gotowa usługa. Naszym zadaniem jest jedynie odpalenie skryptów generujących bazę danych oraz podpięcie usługi do środowiska uruchomieniowego.

Pierwszym etapem jest zatem utworzenie bazy danych:

  1. Otwieramy SQL Server Management Studio. W okienku wykonywania skryptów wpisujemy zapytanie generujące pustą bazę danych:
    CREATE DATABASE WorkflowPersistenceStore
    

  2. Następnie musimy stworzyć niezbędne tabele, procedury oraz schema. Wystarczy odpalić skrypt SqlPersistenceService_Logic.sql oraz SqlPersistenceService_Schema.sql. Oba znajdują się w folderze “%WINDIR%\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\<language>\SqlPersistence_Schema”. W zależności od konfiguracji systemowej może to być np. ścieżka “C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\en”.

Kolejnym krokiem jest konfiguracja aplikacji:

  1. Otwieramy plik app.config  lub web.config i wstawiamy nową sekcję dla workflow:
    <configSections>
        <section
         name="PersistenceRuntimeExample"
          type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, 
                System.Workflow.Runtime, Version=3.0.00000.0, 
               Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     </configSections>

  2. Dodajemy usługę persystencji oraz parametr zawierający connectionstring. Plik konfiguracyjny powinien zatem zawierać poniższy kod:

    <?xmlversion="1.0"encoding="utf-8" ?>
    <configuration>
     
     <configSections>
        <section
         name="PersistenceRuntimeExample"
          type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, 
                System.Workflow.Runtime, Version=3.0.00000.0, 
               Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     </configSections>
     
     <PersistenceRuntimeExample>    
        <CommonParameters>
          <add name="ConnectionString"value="Initial Catalog=WorkflowPersistenceStore;Data Source=localhost;Integrated Security=SSPI;" />
        </CommonParameters>
        
        <Services>
          <add
            type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
                  System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
                  PublicKeyToken=31bf3856ad364e35"
                  UnloadOnIdle="true"/>
        </Services>
     </PersistenceRuntimeExample>
     
    </configuration>

  3. Środowisko uruchomieniowe będzie skonfigurowane automatycznie po przekazaniu nazwy sekcji:
    WorkflowRuntime runtime = new WorkflowRuntime("PersistenceRuntimeExample");

  4. Od tej chwili stan workflow’a jest zapisywany w bazie danych SQL Server.

Alternatywnym ale mniej elastycznym podejściem jest dołączenie usługi persystencji za pomocą kodu:

WorkflowRuntime runtime = new WorkflowRuntime();
SqlWorkflowPersistenceService persistenceService = new SqlWorkflowPersistenceService("Initial Catalog=WorkflowPersistenceStore;Data Source=localhost;Integrated Security=SSPI;");
runtime.AddService(persistenceService);

Odradzam jednak powyższy sposób. Dołączenie usługi za pomocą pliku konfiguracyjnego pozwala na modyfikacje connectionstring’a bez potrzeby ponownej kompilacji co stanowi ogromną zaletę.

Aplikacja kliencka – Windows Workflow Foundation

W jednym z ostatnich postów pokazałem jak stworzyć aplikację WWF na przykładzie prostego Sequential Workflow. Dzisiaj zajmiemy się wykorzystaniem stworzonej biblioteki w aplikacji klienckiej. Warto najpierw ściągnąć kompletny kod źródłowy ponieważ nastąpiła jedna drobna zmiana w implementacji workflow.

Pierwszym etapem jest oczywiście stworzenie aplikacji np. WPF lub Connsole. Następnie należy dodać referencje do skompilowanej biblioteki zawierającej WWF. Ponadto jest jeszcze potrzebna biblioteka System.Workflow.Runtime, System.Workflow.ComponentModel oraz System.Workflow.Activities.

W celu odpalenia workflow trzeba najpierw stworzyć tzw. środowisko uruchomieniowe:

System.Workflow.Runtime.WorkflowRuntime runtime = new System.Workflow.Runtime.WorkflowRuntime();
runtime.StartRuntime();

W poprzednim poście został zdefiniowany specjalny interfejs dla usługi:

[ExternalDataExchange]
public interface IService
{
   void NotifyUser(string message);
   void Approve();        
}

Przykładowa implementacja usługi może wyglądać następująco:

class Service:SimpleWorkflowLibrary.IService
{
   private Guid m_InstanceId;

   public Service(Guid instanceId)
   {
       m_InstanceId = instanceId;
   }                 

   #region IService Members

   void SimpleWorkflowLibrary.IService.NotifyUser(string message)
   {
       Console.WriteLine(message);
   }

   void SimpleWorkflowLibrary.IService.Approve()
   {
       // tutaj powinien znajdować się kod walidacyjny
   }

   #endregion
}

 

Usługa służy do komunikacji między workflow a aplikacją kliencką. Przykładowo stworzony workflow wywołuje metodę NotifyUser (powiadomienie użytkownika) oraz Approve (przeprowadzenie walidacji).

Kolejnym krokiem jest inicjalizacja workflow, przekazanie niezbędnych parametrów oraz dodanie stworzonej usługi:

Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("Number", number);

System.Workflow.Runtime.WorkflowInstance instance= runtime.CreateWorkflow(typeof(SimpleWorkflowLibrary.Workflow1),parameters);
System.Workflow.Activities.ExternalDataExchangeService externalService = new System.Workflow.Activities.ExternalDataExchangeService();            
runtime.AddService(externalService);
Service service = new Service(instance.InstanceId);

externalService.AddService(service);
instance.Start();            

Parametry workflow przekazuje się w metodzie CreateWorkflow za pomocą słownika. Następnie należy dodać usługę Service, zaimplementowaną w poprzednim kroku. Po wywołaniu Start, workflow jest przetwarzany.

Jeśli coś jest niezrozumiałe to najlepiej przejrzeć kod, który znajduje się tutaj lub zapytać (za pomocą komentarza na blogu):).

Prosty Sequential Workflow

Dzisiaj spróbujemy stworzyć pierwszy workflow. Co prawda, nic praktycznego nie będzie wykonywał, ale pokaże kilka mechanizmów, które można wykorzystać w workflow. Zasada działania przykładowego workflow wzorowana jest na przykładzie z MSDN:

  1. Jako parametr wejściowy podajemy liczbę całkowitą,
  2. Workflow sprawdza wartość liczby. Jeśli jest mniejsza niż 2000, kończy działanie wysyłając e-mail. W przeciwnym razie, wymagana jest akceptacja wprowadzonej wartości. Użytkownik musi po prostu wywołać pewnie zdarzenie – wtedy workflow będzie kontynuował pracę.

Do dzieła wiec:

1. Zaczynamy od stworzenia odpowiedniego projektu. Wybieramy “Sequential Workflow Library”.

 

image

 

Po utworzeniu mamy do dyspozycji pusty workflow:

image

Ponadto oprócz wizualnego wsparcia, możemy wszystkie operacje wykonywać bezpośrednio w kodzie. Wygląda to tak jak w przypadku aplikacji WindowsForms – użytkownik może dodawać kontrolki zarówno z poziomu IDE jak i czystego kodu.

2. Przed opracowaniem workflow’a, zdefiniujmy niezbędne parametry wejściowe. W naszym przypadku jest to po prostu liczba całkowita. Wszelkie parametry wejściowe i wyjściowe w WWF definiujemy jako właściwości:

public sealed partial class Workflow1 : SequentialWorkflowActivity
{
   public Workflow1()
   {
       InitializeComponent();
   }
   private int m_Number = 0;
   public int Number
   {
       set { m_Number = value; }
   }
}

Użytkownik będzie mógł w aplikacji hostującej przekazać wartości za pomocą słownika (Dictionary<string,object>). Dokładnie jak to się robi przedstawię w następnym poście kiedy będzie mowa o kliencie workflow.

3. Ponadto potrzebujemy pewnego interfejsu, który będzie stanowił medium komunikacyjne między klientem a workflow. W naszym przypadku będzie on zawierał m.in. metodę odpowiedzialną za wysłanie e-mail’a oraz zdarzenie, które poiwnno przychodzić po zatwierdzeniu przez użytkownika podanej liczby. Co prawda wysłanie e-mail’a może odbywać się bezpośrednio w workflow, jednak uważam, że workflow powinien zawierać wyłącznie logikę a nie szczegóły techniczne.

[ExternalDataExchange]
public interface IService
{
   void NotifyUser(string message);
   void Approve();
   event EventHandler<ExternalDataEventArgs> OnApproved;
}

Interfejs musi być opatrzony również atrybutem ExternalDataExchange. Workflow wywołuje NotifyUser kiedy chce powiadomić użytkownika (np. za pomocą e-mail’a). Jeśli liczba wymaga akceptacji, worklflow wywołuje metodę Approve. Wtedy klient może je zaakceptować poprzez wysłanie zdarzenia OnApproved.

4. Przyszedł czas na implementacje samego workfow’a. Na początku musimy sprawdzić wartość Number, zatem wrzucamy aktywność IfElse:

image Klikamy na pierwszą gałąź (ifElseBranchActivity1) i w oknie properties rozwijamy węzeł Condition (który musi być ustawiony na Declarative Rule Condition). Wybieramy ConditionName. Powinno pojawić się następujące okienko:

image

Tworzymy nowy warunek, wpisując w pole edycyjne:

this.m_Number<2000

Podobne operacje wykonujemy dla drugiej gałęzi, z tym, że jako warunek podajemy m_Number>=2000. Po zakończeniu powinniśmy mieć  utworzone dwa warunki (np. Condition1, Condition2).

5. Gdy liczba jest mniejsza niż 2000, chcemy powiadomić użytkownika za pomocą metody NotifyUser. W tym celu do lewej gałęzi wrzucamy aktywność CallExternalActivity:

image

Ustawiamy właściwość InterfaceType na IService, MethodName na NotifyUser. Z kolei parametr wejściowy message ustawiamy na jakiś komunikat np. “Liczba mniejsza niż 2000”.

image

6. W drugiej gałęzi wywołujemy metodę Approve a następnie oczekujemy na zdarzenie zwrotne OnApproved. Wrzucamy zatem aktywności CallExternalMethod oraz HandleExternalMethod:

image

CallExternalMethod wypełniamy w analogiczny  sposób jak w poprzednim kroku (InterfaceType – IService, MethodName– Approve).

HandleExternalMethod blokuje wykonywanie workflow aż do momentu przyjścia konkretnego zdarzenia. W naszym przypadku chcemy czekać aż przyjdzie OnApproved więc ustawiamy EventName na OnApproved oraz oczywiście InterfaceType na IService.

image

7. Na zakończenie przy wejściu z IF można umieścić jeszcze aktywność CallExternalMethod, która wywołuje metodę NotifyUser przekazując jakiś komunikat.

image

Po kompilacji zostanie utworzona biblioteka dll. W kolejnym poście pokaże jak wykorzystać WWF w aplikacji klienckiej (np. w WPF).

Kompletny kod źródłowy.

Windows Workflow Foundation, część 1 (wprowadzenie)

Omawiając warstwę biznesową wydaje mi się, że warto wspomnieć o technologii WWF. Kilka postów wcześniej pisałem o tzw. silniku reguł biznesowych. Chodziło o zdefiniowanie reguł oraz przepływu informacji w systemie. Jako przykład można podać wysyłanie zamówienia. Proces składa na pewno z kilku etapów takich jak aktualizacja bazy danych, wysłanie e-mail’a  z potwierdzeniem do klienta oraz np. wywołanie metody usługi sieciowej (może to być WCF magazynu). Po kilku miesiącach może okazać się, że scenariusz nie jest już wystarczający ponieważ doszły elektroniczne płatności i w obsłudze zamówienia trzeba uwzględnić czy klient wybrał opcję płatności przelewem(oczekiwanie na zatwierdzenie przelewu), kartą kredytową (walidacja) czy przy odbiorze (natychmiastowa wysyłka po weryfikacji). Oczywiście wszystko można zrealizować w czystym kodzie (tak większość osób dziś robi) i o ile nie popełni się jakiegoś błędu projektowego wszystko będzie sprawnie działać. Windows Workflow Foundation jednak znacznie to upraszcza i za pomocą rozbudowanego IDE często nie musimy pisać nawet czystego kodu.WWF pozwala nam definiować własne scenariusze biznesowe za pomocą różnych tzw. aktywności. Zamiast opisywać może lepiej od razu pokaże gotowy workflow (źródło MSDN):

 

image

Jak widać workflow składa się z pojedynczych aktywności. Większość rzeczy można zrealizować za pomocą IDE, przeciągając po prostu aktywności z toolbox’a. Ponadto jeśli okazałoby się, że w standardowym zestawie brakuje jakieś aktywności można bez problemu napisać własną (przypomina to trochę tworzenie własnego UserControl). Oprócz prostych if’ów oraz reguł, w standardowym zestawie występują również aktywności odpowiedzialne m.in za łączenie się z usługą sieciową, obsługę błędów (try, catch), transakcje, przetwarzanie współbieżne czy wykonanie po prostu fragmentu kodu c#.

Powyższy rysunek przedstawia Sequential Workflow. Rozwiązanie nadaje się dla pojedynczych scenariuszy takich jak złożenie zamówienia czy weryfikacja danych użytkownika. WWF pozwala jednak na zamodelowanie znacznie szerszego fragmentu logiki biznesowej poprzez wprowadzenie maszyny stanów. Projekt maszyny stanów oprócz oczywiście stanów zawiera przejścia (tranzycje) między nimi. Aktywowanie danego stanu może być wykonane np. za pomocą kodu, specjalnej aktywności czy odpalenia oczekiwanego zdarzenia. W systemie sprzedaży maszynę stanów można również wprowadzić do obsługi zamówień. Każde zamówienie ma pewien stan: oczekiwanie na zapłatę, weryfikacja, oczekiwanie na wysłanie, oczekiwanie na feedback. Podczas każdego stanu muszą być wykonywane pewne operacje takie jak  aktualizacja bazy danych czy wysłanie e-mail’a z informacją o zmianie stanu. Przykładowa maszyna stanów, którą miałem okazję ostatnio zaimplementować właśnie dla systemu sprzedaży:

 

image

 

Ponadto każdą aktywność można debuggować tak samo jak zwykły, czysty kod (za pomocą breakpoint, co widać na powyższym screenie).

Domyślnie, wszelkie informacje (zmienne, aktualny stan itp.) są przetrzymywane w pamięci ulotnej. W razie awarii i restartu komputera niemożliwe będzie odtworzenie poprzedniego stanu. Oczywiście w dużej części scenariuszy nie jest to problem ponieważ wykonanie poszczególnych sekwencji trwa bardzo krótko. Jeśli jednak zbudujemy maszynę stanów lub sequential workflow, których wykonanie trwa kilkanaście dni, WWF dostarcza również wsparcie w postaci usługi persystencji. Wszelkie informacje o stanie zamiast być zapisywane w pamięci ulotnej będą trafiały do bazy SQL Server. W takim przypadku maszyna stanów może być ciągle odpalona w tle i wykonywać pewne operacje. Częstym również scenariuszem użycia WWF jest wyeksponowanie go przez WCF.

W następnych postach będę kolejno pokazywał jak zrealizować przedstawione tutaj scenariusze. Cały czas równoległe będę również opisywał wzorce projektowe – przed nami jeszcze warstwa DAL, prezentacji oraz usług. Prawdopodobnie powstanie jeszcze kilka postów o design patterns w warstwie biznesowej. Zapraszam 🙂 !