Değerli Okurlarım Merhabalar,
Workflow Foundation yardımıyla kod akışlarının modellenebilmesi ve herhangibir .Net uygulaması
içerisinden host edilerek çalıştırılabilmesi mümkündür. Bu kavram işin
içerisine servisler girdiğinde çok daha genişlemektedir. Nitekim
servisler yardımıyla Workflow örneklerinin host
edildikleri uygulama dışındaki
ortamlar ile haberleşebilmeleri mümkün olmaktadır. Hatta servislerin
kendi içlerinde Workflow aktivitilerini kullanabilmeleri ve böylece
belirli kod akışlarını yürütebilmeleride mümkündür. Bu cümleden sonra
durup düşünüldüğünde Workflow Foundation kavramının akışları, platform
bağımsız ortamlara taşıyabileceği sonucuda ortaya çıkmaktadır. Diğer
taraftan Workflow uygulamaları sadece dış ortamlar ile haberleşmek için
servislerden yararlanmazlar. İlaveten, Workflow çalışma zamanını(Runtime)
ilgilendiren ve özellikle aktivitilerin dayanıklı olarak
saklanmasını(Durable Persistence), çalışma hayatlarının
izlenmesini(Tracking), adımlar arası geçişlerin özel olarak
planlanmasını(Scheduling) sağlayan ve .Net Framework içerisinde
önceden tanımlanmış servislerde söz konusudur.
 |
Workflow servisleri, çalışma
zamanına eklenerek workflow örneklerine yeni kabiliyetler
kazandırılmasını sağlamak amacıyla kullanılırlar. Örneğin
PersistenceServices ve TrackingServices, SQL veritabanlarını
varsayılan olarak kullanan çalışma zamanı servisleridir. PersistenceServices ile workflow' ların kalıcı olarak
saklanabilmesi sağlanabilir. TrackingServices
yardımıylada çalışma zamanı workflow örneklerinin izlenebilmesi
mümkündür. Bir başka örnek olarak workflow çalışma zamanı
davranışlarını değiştirmemizi sağlayan Manual Scheduler servisi
göz önüne alınabilir. Bu servislerin bir kısmının
kullanılabilmesi için, çalışma zamanına
bilinçli olarak eklenmeleri gerekmektedir. Bir başka deyişle
etkinleştirilmeleri gerekir. |
İşte bu yazımızdaki konumuz, uzun
süreli çalışma ihtimali olan bir Workflow' un belirli
koşullarda kalıcı olarak fiziki bir ortamda saklanmasının, SQL
Persistence Service yardımıyla nasıl gerçekleştirilebileceğidir. SQL
Persistence Service sayesinde, bir Workflow' un faaliyetsiz kalması(Idle) halinde bellek yerine, tablo bazlı bir ortamda saklanabilmesi ve
bu durum sona erdiğinde söz konusu depolama alanından tekrar
ayağa kaldırılarak çalışmaya devam etmesi mümkün olmaktadır. Konuyu daha
kolay kavrayabilmek adına ilk önce Workflow çalışma zamanının kendi ve
yönettiği WF örnekleri ile ilişkili yaşam döngüsünü incelemekte yarar
vardır. Bu yaşam döngüsünün kolayca ele alınabilmesi için
WorkflowRuntime sınıfı içerisine çeşitli olaylar eklenmiştir.
Bilindiği üzere WorkflowRuntime sınıfı
çalışma zamanında WF örneklerinin yönetiminden sorumludur. Bu
yönetim işlemi sırasında WorkflowRuntime sınıfı üzerinden ele
alınabilecek 14 farklı olay metodu vardır. Söz konusu olayların bir
kısmı sadece çalışma zamanını ilgilendirirken, bir kısmıda WF
örneklerinin yaşam döngülerine(LifeCycle) adanmıştır. Buna göre
ServicesExceptionNotHandled, Started ve Stopped olayları Workflow
çalışma zamanı olayları olarak düşünülebilir.
|
Workflow Çalışma
Zamanı Olayları |
| ServicesExceptionNotHandled
|
Workflow çalışma zamanı
servislerinden herhangibirinde kontrol altına alınmamış bir
istisna(Exception) oluştuğunda devreye giren olaydır. |
| Started |
Workflow çalışma zamanı motoru,
üzerine eklenmiş servisler ile başarılı bir şekilde
başlatıldığında devreye girer. Burada çalışma zamanına eklenen
servislerin başarılı bir şekilde başlatıldıklarına dair bir
bilgilendirme yapması söz konusudur. |
| Stopped |
Started olayına benzer
olaraktan, WF çalışma zamanı motorunun, kendi üzerinde yer alan
ve çalışmakta olan tüm servislerin başarılı bir şekilde
durdurulması sonrasında tetiklenir. Servisler başarılı bir
şekilde durdurulduklarına dair WF çalışma zamanı motoruna
bilgilendirmede bulunurlar. |
Aşağıdaki tabloda açıklamaları verilmiş
olan olaylar ise, WF çalışma zamanının yönettiği Workflow örneklerinin
durumlarının(State) değiştiği hallerde tetiklenmektedir.
|
Workflow Örneğine
Adanmış Olaylar |
| WorkflowAborted
|
Workflow örneği devre dışı
bırakıldığında tetiklenir. Özellikle Persistence servisi
kullanıldığında önem kazınır. Nitekim devre dışı bırakılan
servisin kalıcı olarak saklanması ve tekrar kaldığı yerden ayağa
kaldırılması(Resume) mümkün olabilmektedir. |
| WorkflowCompleted
|
Bir Workflow örneği tamamlanıp
bellekten henüz kaldırılmadan önce devreye giren olaydır. Bu
olaya ait metod yardımıyla host uygulamaya, tamamlanan Workflow
örneğinin output parametrelerini döndürmek mümkündür. |
| WorkflowCreated
|
Workflow örneği
oluşturulduğunda ancak Start metodu ile çalıştırılmadan
az önce tetiklenir. |
| WorkflowIdled
|
Bir workflow örneği dışarıdan
beklediği bir etki veya Delay aktivitesi nedeni ile içerisinde
yer alan herhangibir aktiviteyi işletmediği durumlarda
tetiklenir. Özellikle Idle olma durumunda Persistence
hizmetlerinin kullanımı önem kazanır. |
| WorkflowLoaded
|
Persistence servisi
kullanıldığı durumlarda, Workflow örneğinin herhangibir
aktivitesi çalıştırılmadan önce ve belleğe yüklenmesi sonrasında
devreye giren olaydır. |
| WorkflowPersisted
|
Persistence servisin
kullanılması halinde bir workflow örneğinin saklanmak üzere
kaydedilmesi sonrasında tetiklenir. Workflow persistence servisi
varsayılan olarak SQL veritabanını kullandığından, söz konusu
kaydetme işlemi tablo üzerinde gerçekleştirilmektedir. |
| WorkflowResumed
|
Bir erteleme nedeni ile
Suspended moda geçen bir örneğin tekrar ayağa kalkması
sonrasında ve kaldığı yerden devam ederken herhangibir aktivite
çalıştırılmadan önce devreye giren olaydır. |
| WorkflowStarted
|
Workflow örneği yürütülmeye
başlatıldığında tetiklenir. Bu başlangıç kök aktivitenin(Root
Activity) çalıştırılması sonrasında meydana gelmektedir. |
| WorkflowSuspended
|
Workflow örneği, Suspend
metoduna yapılan çağrı veya Suspend aktivitesine gelinmesi
nedeniyle Suspended moduna geçtiğinde tetiklenen olaydır. |
| WorkflowTerminated
|
Workflow örneği yok edildikten
ama bellekten atılmadan az önce çalışan olaydır. Workflow,
Terminate metodu yardımıyla, Terminate aktivitesine gelinmesi
nedeniyle veya ele alınmamış bir istisna(Unhandled Exception)
yüzünden Terminated durumuna geçebilir. Eğer persistence servis
kullanılıyorsa, Terminate edilen workflow örneğine ait tüm
kayıtlar ilgili depolama alanından kaldırılır. SQL tabanlı
persistence servisi göz önüne alındığında bu, tablolar üzerinde
gerekli silme işlemlerinin yapılması anlamına gelmektedir. |
| WorkflowUnloaded
|
Persistence servisleri
kullanıldığında, Workflow örneği depolama alanına kaydedildikten
sonra ama bellekten kaldırılmadan az önce tetiklenen olaydır. |
Aslında WF Çalışma Zamanı Motorunun(WF
Runtime Engine) kendisinin bir State Machine olduğu rahatlıkla
düşünülebilir. Nitekim, yönetmekte olduğu örneklerin durumları
arasındaki geçişleri kontrol altına almakta ve bununla ilişkili olayları
yönetmektedir. Temel olarak workflow örnekleri Created, Running,
Suspended, Completed ve Terminated olmak üzere 5 farklı duruma sahip
olabilir. Bu durum aşağıdaki diyagram ile özetlenebilir.

İlk etapta, Persistence servisinin
devrede olmadığı durumlarda standart olarak çalışan olay metodlarını ele
alacağımız bir örnek geliştirerek devam edebiliriz. Bu amaçla örnek bir
Sequential Workflow Console Application oluşturarak başladığımızı
düşünebiliriz. Söz konusu örnek içerisinde Costflow isimli bir
Sequential Activity kullanılmakta olup adımları aşağıdaki şekilde
görüldüğü gibidir.

Söz konusu aktivite aynı zamanda
dışarıdan parametre alıp, bir sonuç üretmektedir. Aktivitenin
faaliyetsiz(Idle) moda
geçtiğini görmek için sembolik olarak Delay aktivitesinden
yararlanılmaktadır. Söz konusu aktivititede sadece duraksama süresi
özelliği 10 saniye olarak set edilmiştir.

Costflow aktivitesine ait kod içeriği aşağıdaki gibi tasarlanabilir.
using System;
using System.Workflow.Activities;
namespace WFCostFactory
{
public
enum
WorkType
{
Consumer,
Corporate
}
public sealed partial class Costflow
:
SequentialWorkflowActivity
{
#region Workflow
özellikleri(Properties)
// Dış ortamdan gelen parametreler
public int
TotalDays
{ get; set; }
public decimal
CostValue
{ get; set; }
public
WorkType
WorkT
{ get; set; } // Dış ortama sonuç olarak döndürülen parametre
#endregion
public Costflow()
{
InitializeComponent();
}
// CodeActivity tarafından
çalıştırılan örnek fonksiyonellik
private void
Calculate(object
sender, EventArgs e)
{
Console.WriteLine("Calculate Metodu. Maliyet hesaplama işlemleri
yapılır");
switch
(WorkT)
{
case WorkType.Consumer:
CostValue = TotalDays * 1.10M;
break;
case WorkType.Corporate:
CostValue = TotalDays * 1.15M;
break;
default:
CostValue = 1;
break;
}
}
}
} |
Söz konusu aktiviteyi host eden Console
uygulamasına ait kod içeriği ise aşağıdaki gibidir. Burada dikkat
edilmesi gereken nokta WorkflowRuntime örneğine ait tüm olayların
yüklenmiş olmasıdır. Bu olayların çoğu örneğimizde devreye
girmeyecektir. Ancak hangi durumlarda devreye gireceği yukarıdaki
tablolarda belirtilmiştir.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Runtime;
namespace WFCostFactory
{
class Program
{
static
WorkflowRuntime
wfRuntime = null;
static
AutoResetEvent
wHandle = null;
static void Main(string[] args)
{
// Workflow
Runtime nesnesi örneklenir
using(wfRuntime
= new WorkflowRuntime())
{
wHandle = new AutoResetEvent(false);
#region Event Tanımlamaları
wfRuntime.Started
+= new
EventHandler<WorkflowRuntimeEventArgs>(wfRuntime_Started);
wfRuntime.ServicesExceptionNotHandled
+= new
EventHandler<ServicesExceptionNotHandledEventArgs>(wfRuntime_ServicesExceptionNotHandled);
wfRuntime.Stopped
+= new
EventHandler<WorkflowRuntimeEventArgs>(wfRuntime_Stopped);
wfRuntime.WorkflowAborted
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowAborted);
wfRuntime.WorkflowCreated
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowCreated);
wfRuntime.WorkflowIdled
+= new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowIdled);
wfRuntime.WorkflowLoaded
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowLoaded);
wfRuntime.WorkflowPersisted
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowPersisted);
wfRuntime.WorkflowResumed
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowResumed);
wfRuntime.WorkflowStarted
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowStarted);
wfRuntime.WorkflowSuspended
+= new
EventHandler<WorkflowSuspendedEventArgs>(wfRuntime_WorkflowSuspended);
wfRuntime.WorkflowTerminated
+= new
EventHandler<WorkflowTerminatedEventArgs>(wfRuntime_WorkflowTerminated);
wfRuntime.WorkflowUnloaded
+= new
EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowUnloaded);
wfRuntime.WorkflowCompleted+=new
EventHandler<WorkflowCompletedEventArgs>(wfRuntime_WorkflowCompleted);
#endregion
// Workflow nesne örneği oluşturulur
// TotalDays ve WorkT özellikleri için ilk değerler set edilir
WorkflowInstance instance = wfRuntime.CreateWorkflow(
typeof(WFCostFactory.Costflow)
, new
Dictionary<string,object>
{
{"TotalDays",20}
,{"WorkT",WorkType.Corporate}
}
);
// Workflow örneği başlatılır
instance.Start();
// İşlemler tamamlana kadar bekle
wHandle.WaitOne();
}
}
static void
wfRuntime_WorkflowUnloaded(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowUnloaded",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowTerminated(object
sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2} Exception
Message : {3}", DateTime.Now, "WorkflowTerminated",
e.WorkflowInstance.InstanceId.ToString(), e.Exception.Message);
wHandle.Set();
}
static void
wfRuntime_WorkflowSuspended(object
sender, WorkflowSuspendedEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowSuspended",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowStarted(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowStarted",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowResumed(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowResumed",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowPersisted(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowPersisted",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowLoaded(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowLoaded",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowIdled(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowIdled",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowCreated(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowCreated",
e.WorkflowInstance.InstanceId.ToString());
}
static void
wfRuntime_WorkflowCompleted(object
sender, WorkflowCompletedEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowCompleted",
e.WorkflowInstance.InstanceId.ToString());
Console.WriteLine("Maliyet : {0}",
e.OutputParameters["CostValue"].ToString());
wHandle.Set();
}
static void
wfRuntime_WorkflowAborted(object
sender, WorkflowEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, InstanceId : {2}",
DateTime.Now, "WorkflowAborted", e.WorkflowInstance.InstanceId);
}
static void
wfRuntime_Stopped(object
sender, WorkflowRuntimeEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, IsStarted : {2}",
DateTime.Now, "WFRuntime_Stopped", e.IsStarted.ToString());
}
static void
wfRuntime_ServicesExceptionNotHandled(object
sender, ServicesExceptionNotHandledEventArgs e)
{
Console.WriteLine("{0} : InstanceId : {1} Event : {2}, Exception
Message : {3}", DateTime.Now,
e.WorkflowInstanceId.ToString(),"WF
Runtime_ServicesExceptionNotHandled",e.Exception.Message);
}
static void
wfRuntime_Started(object
sender, WorkflowRuntimeEventArgs e)
{
Console.WriteLine("{0} : Event : {1}, IsStarted : {2}",
DateTime.Now, "WFRuntime_Started", e.IsStarted.ToString());
}
}
} |
Örneği ilk etapta bu haliye
çalıştırdığımızda aşağıdaki ekran çıktısı ile karşılaşırız.

İlk analizimi yapabiliriz artık. Dikkat
edileceği üzere ilk olarak Workflow çalışma zamanına ait Started olayı
tetiklenmiş ve IsStarted değeri true olarak set edilmiştir.
Hatırlanacağı üzere Workflow motorunun kullandığı veya başlattığı tüm
servislerin IsStarted özelliğine etkisi vardır ve bu özelliğin değeri
true kalmadığı sürece çalışma zamanı başlatılamayacaktır. Bu işlemin
arkasından Workflow nesnesi örneklendiği için WorkflowCreated ve
WorkflowStared olayları sırasıyla çalışmaktadır. Süreç devam etmekteyken
Delay aktivitesi devreye girmiştir. İşte bu noktada Workflow örneği
faaliyetsiz kalarak, çalışma zamanı moturu tarafından bellekte tutulmaya
devam edilmektedir. Bu anda WorkflowIdled olayı tetiklenmiştir.
 |
Özellikle olay metodlarının
bazıları içerisindeden GUID tipinden InstanceId değerlerinin
elde edilebiliyor olması önemlidir ki bu sayede hangi WF
örneğinin faaliyetsiz kaldığı kolayca anlaşılabilmektedir.
Tahmin edeceğiniz üzere bu Id değeri persistence ortamları
içinde benzersiliği sağlamak açısından önemlidir. |
Faaliyetsiz kalma durumu Delay
aktitivitesinde belirtilen süre sonlanıncaya kadar devam eder. Süre
sonunda ise Workflow örneği çalışmasına kaldığı yerden tekrar
başlayacaktır. İşte bizim en büyük amacımız bu faaliyetsiz kalma anında
söz konusu WF örneğini SQL Persistence Service yardımıyla fiziki bir
ortama kaydetmektir.
Varsayılan olarak WF örneklerinin
durumu bellekte saklanır. Bir başka deyişle, workflow herhangibir
sebeple faaliyetsiz duruma geçtiğinde söz konusu örnek bellekte asılı
olarak kalır ve beklemeye başlar. Ancak gerçek hayat senaryolarında
çalışmakta olan Workflow örneklerinin uzun süre asılı kalmasıda söz
konusu olabilir. Bu, özellikle faaliyetine devam etmesi için onu bekleten
operasyona bağlıdır. Dolayısıyla bu tip vakalarda Workflow örneklerinin
kalıcı olarak saklanmaları tercih edilebilir. Kalıcılıkta esas olan
Workflow örneğinin o anki durumu ve değerleri ile fiziki bir depolama
alanına atılmasıdır. Bu noktada çoğunlukla SQL gibi veritabanı
kaynaklarının kullanılması tercih edilir. Diğer taraftan elbetteki
Persistence servisleri özelleştirilebilir ve farklı veri kaynaklarına
kaydetme işlemleri gerçekleştirilebilir.
Workflow Foundation, çalışma zamanındaki WF örneklerinin bellekten
kaldırılıp çalışmasının durdurulması sonrasında, kalıcı olarak
saklanabilmeleri için önceden geliştirilmiş bir Persistence servisi
sunmaktadır. Hangi tip Persistence kullanılırsa kullanılsın, Workflow
çalışma zamanı, kalıcı olarak saklanan Workflow örneğine(örneklerine) gelen
mesajları takip de eder. Bu sayede gerektiği anda, üzerinde yer alan
Persistence servisi devreye alarak, ilgili WF örneğinin tekrardan
belleğe yüklenmesini sağlayabilir. Workflow çalışma zamanı, Persistence
servisini belirli durumlar gerçekleştirildiğinde çağırmaktadır. Bu
durumlar;
- WF örneği belirli bir nedenden
askıya alındığında yani faaliyetsiz hale geçtiğinde(Idle).
- WF örneği yok edilmeden(Terminate)
önce.
- WF örneği tamamlanmadan(Complete)
önce.
- Workflow örneği üzerinde Unload,
TryUnload metodları çağırıldığında.
- PersistOnCloseAttribute niteliği
ile imzalanmış olan bir aktivitenin tamamlanması sonrasında.
Özellikle transaction kullanan aktivitiler bu niteliğe sahiptir.
Diğer taraftan içerisinde transaction kullanılacak olan
aktivitilerinde bu nitleği uygulaması gerekir.
WorkflowPersistenceService abstract
sınıfından türetme yapılarak istenirse özel persistence sınıflarıda
yazılabilmektedir. Biz örneğimizde SqlWorkflowPersistenceService
tipinden yararlanarak WF örneklerini SQL veritabanı üzerinde
saklamaya çalışacağız. Şimdi bu durumu incelememiz gerekiyor. Ancak
depolama alanı için SQL tarafında gerekli hazırlıkların yapılması
gerekmektedir. Bu noktada
.Net Framework ile birlikte hazır olarak gelen WF SQL betikleri(Scripts)
kullanılabilir. Söz konusu SQL betikleri varsayılan olarak örneğin
Windows XP işletim sisteminin kurulu olduğu bir makinede C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Workflow
Foundation\SQL\EN klasöründe yer almaktadır.

Burada yer alan
SqlPersistenceService_Logic ile SqlPersistenceService_Schema betikleri,
Persistence depolama alanı için gerekli tablo(Table), saklı
yordam(Stored Procedure) gibi veritabanı nesnelerini
oluşturmakla görevlidir. Söz konusu betikler, bir SQL sunucusu üzerinde
çalıştırılıp kullanılabileceği gibi, istenirse ilgili Workflow çalışma
zamanını Host eden uygulamanın erişebileceği dosya bazlı bir veritabanı üzerindende
çalıştırılabilir. Biz örneğimizde ikinci seçeneği kullanacağız. Yani,
söz konusu depolama alanı için SQL Express Edition temelli bir
veritabanı dosyasını ele alacağız. Bu amaçla ilk olarak Solution' ımıza
bir Database Project ekleyerek devam edebiliriz. Proje eklenmesi sırasında
bize aşağıdaki ekran görüntüsünde olduğu gibi kullanmak istediğimiz
veritabanı sorulacaktır.

Elimizde böyle bir veritabanı
olmadığını göz önüne alaraktan Add New Reference seçeneğine tıklayalım.
Sürekli kullandığımız standart bağlantı ekleme iletişim kutusu ile
karşılacağız. Burada önemli olan veri kaynağı olarak Microsoft SQL
Server Database File (SqlClient) tipinin seçilmesidir. Sonrasında ise
veritabanımıza bir isim vererek devam edebiliriz.

Ok düğmesine bastığımızda söz konusu
veritabanı yoksa eğer, oluşturmak isteyip istemediğimize dair bir soru
sorulacaktır. Bu oluşturma işlemi sırasında unutulmaması gereken
noktalardan biriside SQL Express servisinin çalışıyor olması
zorunluluğudur. Eğer servis çalışmıyorsa tahmin edileceği üzere söz
konusu veritabanı oluşturulamayacaktır. Database projesi oluşturulduktan
sonra yukarıda değindiğimiz SQL betiklerini Create
Scripts klasörü
altına ekleyerek devam edebiliriz. Bu işlemler sonrasında proje içeriği
aşağıdakine benzer olacaktır.

(Buradaki gibi bir veritabanı
projesinin oluşturulması aslında şart değildir. Bu sadece söz konusu
veritabanının yönetimin kolaylaştırılmasını sağlayan ve belirli bir
düzeni tesis eden bir opsiyon olarak görülmelidir. Genel olarak gerçek
hayat uygulamalarında depolama alanı olarak sunucu bazlı veritabanları
tercih edilir. Sizlere tavsiyem aynı örneği SQL sunucusu üzerinde
gerçekleştirmeye çalışmanızdır.)
Sıradaki işlem, söz konusu SQL
betiklerinin çalıştırılmasıdır. Bu betikler çalıştırıldıktan sonra
CostFactoryPersistenceDb isimli veritabanının içeriği aşağıdaki şekilde
görüldüğü gibi oluşturulacaktır.

Burada temel olarak Workflow
örneklerinin saklanması(Insert), kilitlenmesi(lock), elde
edilmesi(retrieve) veya silinmesi(delete) ile
ilişkili gerekli Stored Procedure' ler ve tablolar yer almaktadır. Artık
depolama alanıda tanımlandığına göre, Workflow uygulamamız için gerekli
kod değişikliklerini yapabiliriz. Bu amaçla host uygulama üzerinde
SqlWorkflowPersistenceService' in oluşturulması ve çalışma zamanına
eklenmesi gerekmektedir. İşte örnek kodlarımız;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Runtime;
using
System.Workflow.Runtime.Hosting;
namespace WFCostFactory
{
class Program
{
static WorkflowRuntime wfRuntime =
null;
static AutoResetEvent wHandle = null;
static void Main(string[] args)
{
// Workflow
Runtime nesnesi örneklenir
using(wfRuntime = new WorkflowRuntime())
{
// Varsayılan ayarları ile persistence servisi örneklenir
SqlWorkflowPersistenceService persistenceService = new
SqlWorkflowPersistenceService
(
@"Data Source=.\SQLEXPRESS;AttachDbFilename=C:\Documents and
Settings\Burak Selim Senyurt\My
Documents\CostFactoryPersistenceDb.mdf;Integrated
Security=True;Connect Timeout=30;User Instance=True"
);
// Oluşturulan servis çalışma zamanına bildirilir.
wfRuntime.AddService(persistenceService);
//Diğer kod satırları... |
İlk olarak
System.Workflow.Runtime.Hosting isim alanında(namespace) yer alan
SqlWorkflowPersistence hizmetine ait bir nesne örneği oluşturulur. Nesne
örneklenirken yapıcı metod(Constructor) içerisinde persistence için kullanılacak veri
depolama alanın bağlantı bilgisi verilmektedir. Bu en basit yapıcı
metodu versiyonudur. Diğer versiyonlarını kullanarak farklı başlangıç
ayarlamaları yapılabilir. Söz gelimi Workflow örneklerinin Idle moda
geçtiklerinde bellekten kaldırılıp kaldırılmayacakları, birden fazla
Workflow çalışma zamanı moturunun aynı WF örneklerini kullanmaları halinde,
birbirlerini kesmemeleri için kilit sürelerinin(Lock Time) ne olacağı
gibi kriterlerde yapıcı metod parametreleri ile belirlenebilir.
 |
Servis tanımlaması ile ilgili
ayarlar istenirse konfigurasyon dosyasında da yapılabilir. Bunun
için host uygulamaya ait konfigurasyon dosyasında örneğin
aşağıdaki tanımlamaların yapılması yeterlidir.

Tabi host uygulama
içerisinde WorkflowRuntime nesne örneği oluşturulurken
wfRuntime=new WorkflowRuntime("WorkflowRuntime"); şeklinde bir
kullanım söz konusudur. Burada parametre olarak app.config
dosyasındaki section adı verilmektedir. Bu ad benzersizdir. Yani
farklı bir isim olamaz. Diğer taraftan yapıcı metodun bu
versiyonunun çalıştırılabilmesi için(örneğin geliştirdiğimiz
Console uygulamasında) mutlaka System.Configuration assembly'
ının projeye referans edilmesi gerekmektedir. |
Artık uygulamamızı test etmeye
başlayabiliriz. Konuyu kolay takip edebilmek amacıyla geliştirdiğiniz
örneği Debug ederken adım adım ilerlemenizi öneririm. Öncelikle
programın çalışması sonrasındaki ekran görüntüsüne bakalım.

Dikkat edileceği üzere Workflow örneği
faaliyetsiz hale geçtikten sonra(WorkflowIdled olayının tetiklenmesi
sonrası) sırasıyla WorkflowPersisted, WorkflowUnloaded olayları
çalışmıştır. Bir başka deyişle faaliyetsiz kalan Workflow örneği
veritabanındaki ilgili tablolara yazılmıştır. Diğer taraftan faaliyetsiz
kalma süresi dolduğunda ve Workflow akışı tekrar devam etmek istediğinde
sırasıyla WorkflowLoaded ve WorkflowPersisted olayları tetiklenmiştir.
Yani WF örneği tablodan tekrar yüklenerek yürütülmeye devam etmiş ve son
olarakta tamamlanmıştır. Özellikle Workflow faaliyetsiz hale geldiğinde
InstanceState isimli tabloda aşağıdaki ekran görüntüsüne benzer olacak
şekilde bir satır açıldığı ve Delay süresi sona erdikten sonra ise WF
örneğinin tekrar ayağa kaldırılmasıyla birlikte söz konusu satırın
silindiği görülür.

Tahmin edileceği üzere bu satır saklanan
WF örneğine ait bilgileri serileştirerek tutmaktadır. Bu nedenle
özellikle WF içerisinde kullanılan tiplerin, eğer SQL Persistence
hizmeti kullanılıyorsa serileştirilebilir olmalarına dikkat etmek
gerekmektedir. Bu durumu analiz etmek için Costflow aktivitesine
Customer isimli tipten bir özellik
eklenmiştir.
using System;
using System.Workflow.Activities;
namespace WFCostFactory
{
public enum WorkType
{
Consumer,
Corporate
}
public class Customer
{
public
string Name { get; set; }
public int Id {
get; set; }
}
public sealed partial class Costflow
:
SequentialWorkflowActivity
{
#region Workflow
özellikleri(Properties)
// Dış ortamdan gelen parametreler
public int TotalDays { get; set; }
public decimal CostValue { get; set;
}
public WorkType WorkT { get; set; }
// Dış ortama sonuç olarak döndürülen parametre
public Customer
Owner { get; set; }
#endregion
public Costflow()
{
InitializeComponent();
}
// Diğer kod satırları |
Burada hemen bir noktayı vurgulamak
isterim. Söz konusu örnek bu haliyle çalıştırıldığında serileştirme ile
ilişkili herhangibir hata mesajının alınmadığı görülecektir. Bunun
nedeni Customer tipine ait nesne örneğinin WF içerisinde kullanılmamış
olmasıdır. Bu nedenle Workflow nesnesi host uygulamada örneklenirken
aşağıdaki kod değişikliğini yapmamız çalışma zamanında serileştirme
hatasını almamız için gerekli ve yeterlidir.
WorkflowInstance instance = wfRuntime.CreateWorkflow(
typeof(WFCostFactory.Costflow)
, new Dictionary<string,object>
{
{"TotalDays",20}
,{"WorkT",WorkType.Corporate}
,{"Owner",new
Customer{ Id=1000, Name="Burak Selim Şenyurt"}}
}
); |
Örnek bu haliyle çalıştırıldığında
aşağıdaki görüntü ile karşılaşılır.

Dikkat edileceği üzere,
WorkflowPersisted olay metodunun hemen arkasından WorkflowTerminated
olayı tetiklenmiş ve oluşan istisna(Exception) mesajı ekrana
yazdırılmıştır. Bir başka deyişle Workflow örneği tabloya yazdırılamamış
ve istisna fırlatarak sonlanmıştır. İşte bu durumun sebebi Owner isimli
Customer tipinin binary formatta serileştirilebilir tanımlanmamasıdır.
Bu nedenle Customer tipinin Serializable niteliği(Attribute) ile
aşağıdaki kod parçasında görüldüğü gibi işaretlenmesi gerekir.
[Serializable]
public class Customer
{
public string Name { get; set; }
public int Id { get; set; }
} |
Uygulama tekrar denendiğinde sorunsuz
olarak çalıştığı hatta WF örneği oluşturulurken parametre olarak verilen
Customer nesnesinin, WorkflowCompleted olayında (tabloda saklanıp tekrar
elde edilmesi ile birlikte) tedarik edilebildiği görülebilir.

Görüldüğü üzere Workflow örneklerinin,
çalışma zamanında belirli koşulların sağlanması şartıyla bir depolama
alanında saklanması ve sonradan tekrardan ayağa kaldırılıp yürütülmesi
SQL Persistence Service yardımıyla son derece kolay bir şekilde
gerçekleştirilebilmektedir. Elbetteki gerçek hayat koşullarında SQL dışı
kaynakların kullanılmasıda istenebilir. Bu gibi vakalarda söz konusu
hizmet için özel bir geliştirme yapılması gerekmektedir. Böylece geldik bir
makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek
hepinize mutlu günler dilerim.
Örneği
İndirmek İçin Tıklayın
Burak Selim ŞENYURT
MVP (Connected System Developer-2008,C# 2007,2006)