Workflow Hosting - 2
Workflow
Hosting yazı dizimin 2. bölümünde kendi workflow host kütüphanenizi
oluşturmanızı sağlayacağım. Birinci bölümde basit bir uygulama ve bu uygulamayı
host edecek bir uygulama yapmıştım. Host uygulamasının çalışma şeklini inceleyerek
dezavantajları ortaya koymuştum. İkinci bölümde ise bu dez avantajları tamamen
ortadan kaldıran bir yöntemi sizlerle paylaşacağım.
Geliştireceğimiz
Kütüphaneye Genel Bakış
Bu
yazımda anlatacağım kütüphane yapısı, Bruce Bukovics’in workflow uygulamalarını
host etme yaklaşımıdır. Windows Workflow Foundation ile ilgili kitapları
karıştırırken bu yaklaşımı gördüm ve sizlerin de kendi uygulamalarınızı
geliştirirken bu kütüphaneyi kullanmanızı tavsiye ederim.
Kütüphane
3 sınıftan oluşuyor.
1-
WorkflowInstanceWrapper: Her instance’ı WorkflowRuntime
içerisinde izole eden sınıftır. Böylelikle Aynı WorkflowRuntime içerisinde
farklı parameterelerle çalışabilen farklı workflowlar host edebileceğiz.
Aşağıdaki diyagram WorkflowInstanceWrapper sınıfına aittir.

2-
WorkflowLogEventArgs:
WorkflowRuntime
içerisinde bulunan WorkflowInstance larının ve WorkflowRuntime’ın durumlarını
loglarken fırlatacağımız olay için gerekli bir sınıf.

3-
WorkflowRuntimeManager: Yapı içerisindeki en önemi sınıf
olan WorkflowRuntimeManager’ı, içerisinde bulundurduğu WorkflowInstanceWrapper
nesnelerini yönetmek için kullanacağız.

Kütüphanemizi
Geliştirmeye Başlayalım
Kütüphanemizi
geliştirmek için ilk yapmanız gereken bir tane Empty Workflow Project
Başlatmak. Ben Projenin Adına “Cagdas.Workflow.Hosting” ismini verdim.

WorkflowInstaceWrapper
Sınıfını Tanımlayalım
1-
Başlattığınız
Empty Workflow projesine WorkflowInstanceWrapper adında bir sınıf ekleyin.

2-
WorkflowInstanceWrapper
sınıfına Seializable attribute’unu ekleyin. Bu attribute ekleyerek ilerleyen
aşamalarda workflow’unuzu serialize etme fırsatı bulacaksınız.

3-
WorkflowInstanceWrapper.cs
uzantılı dosyaya System.Workflow.Runtime, System.Workflow.Runtime.Hosting ve
System.Threading isim alanlarını ekleyin.
4-
Sınıfın
fieldlarını tanımlayın. __workflowInstance field’ı, WorkflowInstanceWrapper
sınıfının izole edeceği WorkflowInstance’tır. WorkflowInstanceWapper sınıfı
kullanılırken arka planda WorkflowInstance nesnesini kullanacak. __waitHandle
field’ı ise WokrflowInstanceWrapper nesnesinin işi bittiğinde Workflow Runtime
Engine ile haberleşmesi için kullanacağız. __outputParameter field’ını workflow’un
döndüreceği parametreleri tutmak için kullanacağız. Workflow çalışırken hata
oluştuğunda, hatayı Workflow Runtime Engine’e bildirmek için __exception
field’ını kullanacağız. Herhangi bir nedenden dolayı workflow’un çalışması
durdurulursa, durdurulma nedenini __reasonSuspended fied’ı aracılığıyla
Workflow Runtime Engine’e bildireceğiz.

5-
Sınıfın
contructor’ını yazın. Contructor’da dışarıdan aldığımız WorkflowInstance
tipindeki parametreyi izlode edeceğiz. WorkflowInstanceWrapper sınıfı kendi
içerisinde direkt olarak bir WorkflowInstance oluşturmuyor, dışarıdan aldığı
parametreyi izole ediyor.

6-
Exception
özelliğini tanımlayın.

7-
Id özelliğini
tanımlayın. Kullanacağımız WorkflowInstance’ın InstanceId’sini verir. Bu
özelliklik birden fazla WokflowInsantance nesnesinin aynı ortamda bulunduğu
noktalarda işe yarayacaktır.

8-
OutputParameters
özelliğini tanımlayın. WorkflowInstance’a ait çıktı parametrelerine
erişeceğimiz özellik.

9-
ReasonSuspended
özelliğini tanımlayın.

10-
WaitHandle
özelliğini tanımlayın.

11-
StopWaiting
metodunu tanımlayın. StopWaiting metodu WorkflowInstace’ın çalışmasını
tamamladığını Workflow Runtime Engine’e haber vermek için kullanacağız.

WorkflowLogEventArgs
Sınıfını Tanımlayalım
1-
Projenize
WorkflowLogEventArgs adında bir sınıf ekleyin.
2-
Message adında
ve string tipinde bir özellik tanımlayın.

3-
Sınıfa ait bit
tane constructor tanımlayın.

WorkflowRuntimeManager
Sınıfını Tanımlayalım
- Sınıf
içerisine aşağıdaki isim alanları ekleyin.

- Sınıf
içerisine field ları ekleyin. __workflowRuntime filed’ını WorkflowInstance
larını yönetmek için kullanacağız. Bu sınıf içerisinde WorkflowInstance
lara direkt olarak erişim yok. WorkflowInstaceWrapper sınıfı ile her
WorkflowInstance nesnesi izole edilmekte.__workdflows field’ını
WrokflowRuntimeManager sınıfı içerisideki WorkflowInstanceWrapper
tipindeki elemanlara erişmek için kullanacağız.

- WorkflowRuntime
özelliğini tanımlayın. WorkflowRuntimeManager sınıfının içerisine
constructor’da parametre olarak verilen WorkflowInstance’ına erişim
sağlıyoruz.

- Workflows
özelliğini tanımlayın. Bu özellik, sınıf içerisindeki
WorkflowInstanceWrapper koleksiyonuna erişim sağlıyor. Yönetimi kolay
olsun diye, Guid ve WorkflowInstanceWrapper tipindeki Dictionary yapısı
içerisinde saklama işlemini yapıyoruz.

- LogStatus
metodunu tanımlayın. LogStatus metodu, Workflow Runtime Engine’in olayları
tetiklendiğinde hangi olayın tetiklendiğini ve hangi olayın hangi
WorkflowInstance için tetiklendiğini kayıt altına almak için
kullanacağımız bir metottur. Bu metot sonunda sıfın en başında tanımlanan
MessageEvent’i tetiklenir.

- FindWorkflowInstance
metodunu tanımlayın. Bu metot verilen Guid tipindeki workflowId’ye göre
sınıf içerisindeki Workflows özelliğinde tutulan koleksiyon yapısında
arama yapar. Eğer o workflowId’ye sahip bir eleman varsa onu geri
döndürür.

- SubscribeToEvents
metodunu tanımlayın. Gelen WorkdflowRuntime nesnesinin bütün olaylarına
abone olan metot. Bu olaylar tetiklendiğinde, WorkdflowRuntime nesnesinin
bütün durumlarını yakalamış oluyoruz. Buradan sonra 20. adıma kadar
aşağıdaki olaylar gerçekleştiğinde çalışacak metotları tanımlayacağız.

- runtime_WorkflowUnloaded
metodu WorkflowInstance kaldırılırken çalışacaktır.
- runtime_WorkflowTerminated
metodu WorkflowInstance içerisinde hata oluştuğunda çalışacaktır. Bu metot
içerisinde durumu kayıt altına aldıktan sonra, hangi workflow’da hata
çıkmışsa onun InstaceId’sini alıp kendi koleksiyonumuz içerisindeki ilgili
WorkflowInstanceWrapper nesnesini buluyoruz. Wrapper nesnesinin Exception
özelliğine workflow içerisinden fırlatılan hatayı koyuyoruz. Böylelikle
istediğimiz zaman WorkflowInstance içinde hangi hata çıkmış görebileceğiz.

- runtime_WorkflowSuspended
metodu WorkflowInstance beklemeye alındığında çalışacaktır. Bizde burada
beklemeye alından workflow’u koleksiyonumuz içerisinden bulup,
ReasonSuspended özelliğine gelen hatayı koyuyoruz. Böyle yaparak herhangi
bir anda WorkflowInstanceWrapper nesnesinin neden beklemeye alındığını
görebiliriz.

- runtime_WorkflowStarted
metodu WorkflowInstance çalışmaya başladığında çalışacaktır.

- runtime_WorkflowResumed
metodu WorkflowInstance suspend olduktan sonra çalışmaya başladığında
çalışacaktır.

- runtime_WorkflowPersisted
metodu WorkflowInstance kalıcı hale getirildiğinde çalışacaktır.
WorkflowInstance ları SqlServer içerisinde kalıcı hale getirebiliyoruz.

- ?runtime_WorkflowIdled
metodu WorkflowInstance çalışmaya işlemini beklemeye aldığında
çalışacaktır.

- runtime_WorkflowCreated
metodu WorkflowInstance oluşturulduğunda çalışacak metottur.

- runtime_WorkflowCompleted
metodu WorkflowInstance çalışmasını bitirdiğinde çalışacak metottur. Bu
metot içerisinde WorkflowRuntimeManager sınıfı içerisindeki çalışmasını
tamamlayan WorkflowInstanceWrapper nesnesinin OutputParameters özelliğine
WorkflowCompletedEventArgs ile gelen e parametresindeki OutputParameters
özelliğinin değeri atanır. Böylelikle WorkflowInstanceWrapper nesnelerinin
çıktı parametrelerinin tipleri, sayıları… gibi farklılıkları da
yakalayabiliriz.Wrapper nesnesinin StopWaiting metodunu çalıştırarak
Workflow Runtime Engine’ wrapper’ın işini bitirdiğini haber veririz.

- runtime_WorkflowAborted
metodu WorkflowInstance’ın çalışması iptal edildiğinde çalışacak metotdur.

- runtime_Stopped
metodu WorkflowRuntime’ın çalışması durduğunda çalışacak metotdur.

- runtime_Started
metodu WorkflowRuntime’ın çalışması başladığında çalışacak metotdur.

- Sınıfın
constructor’ını tanımlayın. Burada dışarıda oluşturuşmuş bir
WorkflowRuntime nesnesini sınıfa kabul ediyoruz. Yönettiğimiz nesne
dışarıdan gelen WorkflowRuntime nesnesidir. Eğer gelen nesne null ise
NullReferenceException fırlatıyoruz. Eğer dışarıdan parametre olarak gelen
nesne null değilse, SubscribeToEvents metodunu çağırarak WorkflowRuntime
nesnesinin bütün olaylarına abone oluyoruz. Bir önceki yazımda
geliştirdiğimiz host uygulamasında sınırlı sayıda olay ile
çalışabiliyorduk. Burada ise WorkflowRuntime nesnesinin her olayını
yakalayarak, her durumunu loglama imkânı buluyoruz.

- AddWorkflowInstance
metodu gelen WorkflowInstance tipindeki instance parametresini Workflows
koleksiyonuna ekler.

- StartWorkflow
metodu belirtilen tipteki workflowu oluşturur ve dışarıdan alınan girdi
parametreleriyle çalıştırır. Daha sonra oluşturduğu instance’ı sıfın
içerisindeki Workflows koleksiyonuna WorkflowInstanceWrapper olarak ekler.

- Clear
metodu, Workflows koleksiyonu içerisindeki dışarıdan parametre olarak
alınan workflowId’ye sahip elemanı siler.

- ClearAllWorkflows
metodu, Workflows koleksiyonu içerisindeki bütün elemanları siler.

- WaitAll
metodu, WorkflowRuntimeManager sınıfı içerisindeki tüm workflowlar
işlerini bitirene kadar bekler. Böylelikle Bütün workflow çalışırlar,
çıktılarını verirler ve çıktılar işlenmek için hazırlanmış olur.

- WorkflowRuntimeManager
sınıfına IDisposable interface’ini implement ettirin. Dispose metodu
içerisinde ilk önce __workflowRuntime nesnesini durdurun, sonra __workflowRuntime
nesneisnin Dispose metodunu çağırın.

Buraya
kadar WorkflowInstanceWrapper, WorkflowLogEventArgs ve WorkflowRuntimeManager
sınıfını tanımlamış olduk. Buradan sonraki aşamalarda workflow kalıcılığını
sağlamak için Sql Server’da bazı işlemler yapacağız. Son olarak bir tane test
uygulaması geliştirerek Host kütüphanemizi test edeceğiz.
Workflow’un
Sql Server Yapılandırması
Bu bölüm
ilerleyen aşamalarda daha çok işinize yarayacak. Özellikle State Machine
Workflow konusuna geldiğimde bu yapılandırmanın neden önemli olduğunu
anlayacaksınız. Bu uygulama içerisinde workflow’un kalıcılığını sağlayabilmek
için Sql Server içerisinde bir veri tabanı oluşturmamız gerekiyor. Bu
veritabanının şablonunu aşağıdaki adreste bulunan scriptler aracılığıyla sağlayacağız.
Şimdi aşağıdaki klasörü açın.

Ben
burada Sql Server Express Edition kullanıyorum. Server’a bağlantı yaparken
authentication modeli olarak SQL Server Authentication kullanıyorum.
Server’ınıza bağlanın.

WorkflowPersistence
adında bir veritabanı oluşturun. Bu veri tabanının adının özel bir anlamı yok.
İstediğiniz isimde veritabanı oluşturabilirsiniz.

Veritabanını
oluşturduktan sonra yukarıda gösterdiğim klasör altındaki
SqlPersistenceService_Schema.sql script’ini çalıştırın.

Daha sonra SqlPersistenceService_Logic.sql script’ini
çalıştırın.

Bu iki
script’i çalıştırarak Workflow ların ihtiyaç duyduğu veritabanı yapısına sahip
olacaksınız. Bu yapılandırmayı uygulamamızı test ederken kullanacağız.

Test
Uygulaması Geliştirelim
İlk başta
Solution Explorer pencrenize baktığınızda aşağıdaki çıktıyı göreceksiniz.

- Uygulamanıza
bir önceki makalemde anlattığım workflow uygulamasını ekleyin. Eklediğiniz
uygulamanın Program.cs dosyasını silin ve uygulamaya WorkflowTest adında
bir sınıf ekleyin.

- Eklediğiniz
projenin referanslarına Cagdas.Workflow.Hosting’i ekleyin. Eklediğiniz
WorkflowTest sınıfının içerisine static ve geriye void döndüren Run adında
bir metot etkleyin. Bu metot içerisine aşağıdaki kodları yazın.
Bu metot içerisinde ilk başta geliştirdiğimiz
Cagdas.Workflow.Hosting isim alanının altında bulunan WorkflowRuntimeManager
sınıfının bir örneğini oluşturuyoruz. Oluşturma işlemini using bloğu içerisinde
yaptığımızdan, using bloğundan çıkıldığında oluşturduğumuz manager nesnesi
otomatik olarak dispose edilecektir. Daha sonra manager nesnesinin MessageEvent
olayına abone oluyoruz. AddServices Metodunu kullanarak WorkflowRuntime
nesnesinin kalıcı olmasını sağlıyoruz. wfArguments koleksiyonu içerisinde girdi
parametrelerini belirlioruz ve ilk oluşturduğumuz WorkflowInstance nesnesi için
“bir“ parametresini belirliyoruz. Daha sonra manager nesnesinin StartWorkflow
metodunu kullanarak Workflow1’i çalıştırıyoruz. Bu işlemi 3 kere daha
yapıyoruz.
Normalde bu işlemler çalışmak için birbirlerini
bekleyeceklerdi fakat biz çalıştırma işlerini yaptıktan sonra manager’ın
WaitAll metodunu kullanarak manager içerisinde bulunan tüm WorkflowInstance
ların işlerini tamamlamasını bekliyoruz. Bütün hepsi işlerini tamamladığında
teker teker çıktıları ekrana yazdırarak metodu sonlandırıyoruz. Bu arada
WorkflowInstance lar çalışırken fırlatılan olayları manager_MessageEvent metodu
içerisinde ekrana yazdırıyoruz.

- Manager_MessageEvent
metodunu tanımlayın.

- AddServices
metodunu tanımlayın. Bu metot içerisinde gelen WorkflowRuntime nesnesini
Core Workflow Servislerinden biri olan SqlWorkflowPersistentService’e
ekliyoruz. Ekleme işlemini yaparken bir tane connection string’e
ihityacımız var. SqlWorkflowPersistentService workflow’un kalıcılığını sağlayan
servistir.

- Projeniz
içerisindeki Workflow1’i açın ve Code aktivitesinin altına bir tane delay
aktivitesi ekleyin. Delay aktivitesini Workflow’un Idled gibi olaylarını
yakalamak için kullanacağız.

- Projeniz
içerisine bir tane program.cs dosyası ekleyin ve Main metodu içerisine
aşağıdaki kodları yazın.

Burada sadece WorkflowTest sınıfının Run metodunu
çalıştırıyoruz.
- Uygulamanızı
F5 tuşuna basarak çalıştırın. Aşağıdaki ekran görüntüsünü elde
edeceksiniz.

Bu ekran görüntüsüne dikkat edecek olursanız, ilk önce
workflowlar oluşturuluyor, çalıştırılıyor, parametreleri yollanıyor ve son
olarak çıktı parametrelerinin değerleri ekrana yazdırılıyor. Bir önceki yazımda
anlattığım karmaşık düzenden arındık.
WorkflowRuntime
Sınıfını App.Config Dosyasında Yapılandırmak
İsterseniz
AddServices metodunu kullanmak yerine App.Config dosyası içerisinde yapacağınız
yapılandırma ile WorkflowRuntime nesnenizi oluşturabilirsiniz. Bunun için ilk
yapmanız gereken uygulamanıza bir tane applicaton configuration file eklemek. Ekleme
işlemini yaptıktan sonra aşağıdaki xml yapısını App.Config dosyası içerisine
yazın. Burada AddService metodu içerisinde yaptığınız işi xml olarak
tanımlıyorsunuz. Birazdan AddSercice metodunu kaldıracaksınız. Aşağıdaki XML
yapısında WorkflowRuntime adında bir bölüm oluşturduk.

Uygulamanıza
System.Configuration referansını ekleyiniz. Daha sonra WorkflowTest sınıfına
gelin ve Run metodunun başında aşağıdaki değişiklikleri yapın.

Burada
AddServices metodunu yorum haline getirdik. Ayrıca new
WorkflowRuntime(“WorkflowRuntime”) bölümünde ayarların App.Config dosyasından
alınmasını sağladık. Böylelikle ayarlarda değişiklik yaptığımızda kodu tekrar
derlememize gerek kalmayacak. Yani bize esneklik kazandıran bir işlem yapmış
olduk.
Uygulamayı
tekrar çalıştırdığınızda problemsiz bir şekilde çalıştığını göreceksiniz.

Özet
- WorkflowRuntime
ve WorklfowInstance nesneleirnin tekrar kullanılabilirliğini arttırıdık.
Kısa ve uzun vadede işe yarar bir işlem.
- Geliştirilen
host uygulaması birden fazla WorkflowInstance nesnesini host edebiliyor.
- Her
WorkflowInstance farklı output parametreleri yönetimi yapılabiliyor.
- Aynı
host uygulaması içerisinde birden fazla farklı tipte WorklfowInstance host
edilebiliyor.
- Her
workflow için ayrıntılı olay yakalama mekanizmasına sahip.
Böylelikle
Workflow hosting makale serisinin son bölümü olan ikinci bölümünü de tamamlamış
oldum. Sizin için Workflow Host işleminin Bruce Bukovics yaklaşımının faydalı
olacağını düşünüyorum. Bir sonraki WWF makalemde görüşmek üzere…
|