Archive for Bez kategorii
NGinn MessageBus – opublikowany
Wcześniej umieściłem tu obwieszczenie o udostępnieniu projektu NGinn MessageBus jako open source, z tym że wpis był po angielsku. Taka mieszanina językowa wyglądała średnio, dlatego ten i przyszłe obcojęzyczne wpisy powędrowały na osobny blog pod adresem http://nginn.vipserv.org/ngblog_en/. Tu będzie tylko po polsku.
Zatem informuję ponownie że projekt NGinn MessageBus jest dostępny publicznie, za darmo i do wszelkich celów. Znajduje się pod adresem http://code.google.com/p/nginn-messagebus/. Mówiąc krótko, jest to asynchroniczna szyna komunikatów (service bus) zrealizowana w oparciu o kolejki komunikatów zapisane w bazie SQL Servera i obsługująca ich dystrybucję między odbiorcami na zasadzie publish-subscribe. NGinn MessageBus stanowi podstawę na której opiera się mój projekt NGinn ale z uwagi na swoje uniwersalne przeznaczenie można go używać w aplikacjach różnego rodzaju – starałem się żeby był jak najłatwiejszy do skonfigurowania i uruchomienia.
Z nowości: wyposażyłem N. MessageBus w interfejs HTTP (wbudowany malutki serwerek www) który pozwala przesyłać komunikaty po http między kolejkami oraz udostępnia podstawowe informacje o działaniu szyny (np wydajnościowe) za pośrednictwem przeglądarki. Dodatkowo konstrukcja tej biblioteki pozwala na dość łatwe (teoretycznie, bo nie próbowałem) użycie innych mechanizmów kolejkowych w miejsce SQLa. Enjoy.
Wdrożenie NGinn – case study
NGinn nie tak dawno temu przeszedł swój chrzest bojowy – mianowicie doczekał się wdrożenia produkcyjnego i to na całkiem sporą skalę. Ponieważ od wdrożenia minęło już trochę czasu a system jest ustabilizowany i można spokojnie powiedzieć że działa, to dziś opowiem nieco o tym jak owo wdrożenie wyglądało i jaka jest w nim rola NGinn.
Krótko o procesie
NGinn został użyty do zautomatyzowania procesu indeksowania dokumentów w firmie telekomunikacyjnej która jest jednym z operatorów komórkowych w Polsce. Indeksowanie dokumentów jest to jeden z etapów procesu obsługi wchodzących do firmy dokumentów papierowych (listów, umów i innych dokumentów dotyczących klientów firmy), który przedstawia się mniej więcej tak:
- Przyjęcie dokumentów w kancelarii
- Sortowanie (wstępna klasyfikacja)
- Przygotowanie do skanowania (rozpakowanie, rozszycie etc)
- Skanowanie
- Indeksacja (klasyfikacja, opisanie dokumentu odpowiednimi danymi)
- Umieszczenie dokumentu w archiwum elektronicznym
- Dalsza obsługa dokumentu już w wersji elektronicznej (dalsze etapy są realizowane w innych systemach, proces obsługi jest różny dla różnych dokumentów)
W skrócie proces ten prowadzi do przekształcenia dokumentu na jego obraz cyfrowy (skan) z odpowiednimi metadanymi, dzięki czemu dalsza obsługa dokumentu może być prowadzona za pomocą systemów informatycznych. Firma dąży do maksymalnego zautomatyzowania tego procesu gdyż obsługuje dziesiątki tysięcy dokumentów dziennie i każda czynność ręczna generuje duże koszty (w obsługę wchodzących dokumentów zaangażowane jest kilkadziesiąt osób).
Celem naszego wdrożenia było zautomatyzowanie etapu indeksacji – czyli etapu w którym osoba skanująca dokument opisuje go odpowiednimi metadanymi. Te metadane zależą od rodzaju dokumentu, ale najczęściej są to podstawowe informacje jak rodzaj dokumentu, data wpłynięcia do firmy, data skanowania, identyfikator klienta, nr telefonu tego klienta, nr dokumentu, kod kreskowy, segment klienta i kilka innych atrybutów. Do tej pory musiały być odczytywane z dokumentu (lub wyszukiwane w innych systemach) i wpisywane ręcznie przez pracownika skanującego dokument, co miało swoje wady: wydłużało czas obsługi dokumentu i stwarzało ryzyko wystąpienia błędów w danych. W celu poprawienia efektywności tego etapu firma opracowała jego wersję zautomatyzowaną w której praca ręczna została albo całkowicie wyeliminowana, albo ograniczona do minimum. Automatyzacja polegała na zmianie aplikacji skanującej tak aby sama wypełniała pola które może (datę skanowania, login osoby skanującej, kod kreskowy i kilka innych), zaś reszta danych pozyskiwana jest automatycznie z systemów zewnętrznych. Na przykład: jeśli na dokumencie występuje kod kreskowy z systemu sprzedaży to dane klienta są pobierane z tego systemu. Jeśli kodu kreskowego brak to konsultant ma za zadanie wpisać ręcznie tylko identyfikator klienta – pozostałe dane klienta zostaną pobrane z systemu billingowego. Konsultant musi interweniować w sytuacjach wyjątkowych, np jeśli np kod kreskowy jest nieczytelny dla aplikacji skanującej (wtedy albo wpisuje go ręcznie albo nakleja nowy kod i podaje numer dokumentu). Procedura zautomatyzowana obejmuje również walidację danych (wykrywanie brakujących lub błędnych numerów klienta/numerów dokumentu/kodów kreskowych). W przypadku wykrycia nieprawidłowości odpowiedni pracownicy otrzymują zadanie poprawy indeksów w dokumencie.
Oszczędność na pojedynczym dokumencie jest rzędu minuty-dwóch. Ale po pomnożeniu tego przez ok 10-15 tysięcy dokumentów dziennie wychodzi nam oszczędność na poziomie trzydziestu osobo-dni (każdego dnia). Oznacza to trzydzieści etatów których nie trzeba tworzyć – czyli niewielkie w sumie usprawnienie w tak masowym procesie daje całkiem niezły efekt finansowy.
Realizacja
Do zrealizowania tej zautomatyzowanej procedury indeksacji użyliśmy NGinn. Działa to tak że po zeskanowaniu dokumentu jest on umieszczany w systemie EDMS (elektroniczne archiwum dokumentów), który to system powiadamia NGinn o pojawieniu się nowego dokumentu. W tym momencie NGinn uruchamia proces automatycznej indeksacji który odpowiada za uzupełnienie dokumentu o odpowiednie metadane. Oto schemat tego procesu:

- Pobranie dokumentu z systemu EDMS.
Po zeskanowaniu dokumentu i umieszczeniu go w systemie EDMS system ten powiadamia NGinn o pojawieniu się nowego dokumentu przekazując wewnętrzny identyfikator tego dokumentu. Na podstawie tego identyfikatora NGinn pobiera dane dokumentu z systemu EDMS. Pobierane są m. in. typ dokumentu, nr dokumentu, kod kreskowy i identyfikator klienta. - Pobranie danych klienta z systemu bilingowego. Jeśli dokument zawiera identyfikator klienta a nie zawiera kodu kreskowego, odpytujemy system bilingowy o dane klienta na podstawie podanego numeru klienta. Odpytanie odbywa się poprzez wywołanie odpowiedniego web service. Jeśli dane klienta zostaną odnalezione idziemy do kroku 4, w przeciwnym razie do kroku 5.
- Pobranie danych dokumentu z systemu sprzedaży – dokumenty z systemu sprzedaży zaopatrzone są w odpowiedni kod kreskowy, identyfikowane są też numerem dokumentu. NGinn odpytuje system sprzedażowy o dane tego dokumentu na podstawie kodu kreskowego lub nr dokumentu, wśród danych zwracanych przez system sprzedażowy są również informacje o kliencie. Jeśli dokument zostanie odnaleziony przechodzimy do kroku 4, w przeciwnym razie do kroku 5.
- Aktualizacja dokumentu w EDMS. Jeśli dane klienta/dokumentu zostały odnalezione w odpowiednim systemie, NGinn aktualizuje dane dokumentu w EDMS dzięki czemu jest on opisany pełnymi metadanymi.
- Zadanie ‘popraw indeksy’. Jest to zadanie ręczne zakładane w systemie obsługi zgłoszeń. Zadanie to pojawia się na liście spraw odpowiedniej grupy konsultantów (system obsługi zgłoszeń jest uniwersalnym systemem w którym rejestrowane są zgłoszenia klientów oraz inne rodzaje spraw, m.in. zadania). Zadanie zawiera odnośnik do dokumentu w EDMS oraz informacje o tym które indeksy należy poprawić. Po poprawieniu indeksów konsultant zgłasza zakończenie realizacji zadania i proces powtarza się od kroku 1. Istnieje możliwość zakończenia zadania z opcją ‘pomiń walidację’, która powoduje przejście do kroku 6 bez dalszej walidacji danych dokumentu – jest to potrzebne w wyjątkowych przypadkach, np gdy dane klienta w na dokumencie są nieaktualne.
- Poprawny przebieg procesu indeksacji kończy się przekazaniem dokumentu do systemu obsługi zgłoszeń. NGinn przekazuje dane dokumentu do systemu obsługi zgłoszeń, który na podstawie tych danych zakłada odpowiedniej treści zgłoszenie i przypisuje je do Back Office, do grupy konsultantów odpowiedzialnych za obsługę danego rodzaju zgłoszeń. NGinn na kroku 6 kończy swoją rolę i nie bierze udziału w obsłudze zgłoszenia.
W celu zaimplementowania tego procesu do standardowego zestawu zadań NGinn musiały zostać dołączone dwa typy zadań służące do integracji z systemem EDMS: zadanie “Pobierz dokument z EDMS” (krok 1) oraz “Zaktualizuj dokument EDMS”, czyli krok 4. Zadania te wykorzystują API integracyjne systemu EDMS. Pozostałe etapy procesu zostały zrealizowane za pomocą zadań standardowo udostępnianych przez NGinn. Dodatkowo, w systemie obsługi zgłoszeń został dodany interfejs integracyjny dla NGinn pozwalający NGinn zakładać zadania w tym systemie oraz raportować do NGinn zakończenie realizacji zadań przez konsultantów.
Jak łatwo policzyć, proces indeksacji wykorzystuje funkcje czterech różnych systemów: EDMS, billingu, systemu sprzedażowego oraz systemu obsługi zgłoszeń. Można powiedzieć zatem ze oprócz funkcji narzędzia ‘BPM’ NGinn pełni też rolę narzędzia integracyjnego zarządzającego przepływem danych między poszczególnymi aplikacjami.
Przebieg wdrożenia
Projekt był realizowany metodą szybkiego prototypowania. Klient bardzo szybko (w ciągu 2 tygodni) od rozpoczęcia prac otrzymał pierwszą wersję oprogramowania do testów. Podczas testów oprócz pewnej liczby błędów wykryto też rozmaite przypadki nietypowe które nie były przewidziane w wymaganiach. Każdy taki wyjątek wymagał modyfikacji procesu – praktycznie na bieżąco dostarczaliśmy klientowi zmodyfikowaną wersję procesu i testy mogły być kontynuowane. W sumie testowane było 7 albo 8 wersji procesu, każda kolejna zawierała obsługę przypadków nie przewidzianych przez poprzednią iterację. Testy trwały około 3 tygodni, później nastąpiło przeniesienie funkcjonalności na środowisko produkcyjne. W środowisku produkcyjnym wykryto jeszcze jeden albo dwa ‘nietypowe’ przypadki wymagające pilnego obsłużenia, co zaowocowało dwiema kolejnymi wersjami procesu.
Podczas testowania niektóre z cech NGinn okazały się bardzo przydatne:
- możliwość używania kilku wersji procesu jednocześnie. W przypadku testów ‘na produkcji’ używana była stabilna wersja procesu zaś eksperymentalne były uruchamiane tylko na potrzeby testów. Po przetestowaniu nastąpiło przełączenie domyślnej wersji procesu na nową według której były obsługiwane kolejne dokumenty. Nie było potrzeby migracji procesów w toku – dokończyły one swój bieg według starej wersji.
- zmiany definicji procesu ‘w locie’, bez zatrzymywania systemu pozwalały na bieżąco uwzględniać uwagi testerów a na produkcji przełączać się między wersjami procesu w sposób niezauważalny dla użytkowników
Po wdrożeniu
Obserwacja systemu po wdrożeniu pokazała kilka faktów:
- Mimo niezbyt rozbudowanej konfiguracji serwera nie ma żadnych problemów wydajnościowych, NGinn obciąża procesor i pamięć w niewielkim stopniu. Baza danych bez problemu radzi sobie z obsługą danych generowanych przez NGinn.
- Po ustabilizowaniu definicji procesu system działa praktycznie bezproblemowo 24 godziny na dobę, 7 dni w tygodniu. Przez ponad miesiąc nie było żadnego problemu technicznego, wszystkie dokumenty zostały zaindeksowane zgodnie z definicją procesu. Mam doświadczenie w pracy z różnymi systemami i naprawdę rzadko zdarza się żeby system działał tak stabilnie zaraz po uruchomieniu produkcyjnym.
- NGinn jest bardzo odporny na awarie systemów zewnętrznych. W naszym procesie indeksacji biorą udział cztery systemy i dość często zdarza się że któryś z nich na jakiś czas ‘wypada’, np nie można się z nim połączyć lub zgłasza błędy. W takiej sytuacji NGinn jest w stanie przeczekać okres niedostępności systemu i wznowić procesy kiedy system zewnętrzny zacznie odpowiadać. Dzieje się tak dzięki mechanizmowi ponawiania w NGinn który przez okres max. 3 dni próbuje ponownie wykonać nieudane operacje. Oznacza to też że po ustąpieniu awarii administrator systemu nie musi nic robić żeby obsługa procesów w NGinn była kontynuowana.
- Standardowo NGinn przechowuje stan wszystkich zadań w bazie danych. Informacje te nie są usuwane po zakończeniu zadania, dlatego ilość gromadzonych danych jest bardzo duża. Proces indeksacji składa się z 6-ciu zadań, co prawda nie wszystkie występują w każdym przebiegu, ale oznacza to że dla każdego indeksowanego dokumentu zapisywane jest 3-6 rekordów w bazie NGinn, co dziennie daje 50-100 tysięcy rekordów. Konieczny jest jakiś mechanizm czyszczący który będzie usuwał stan zakończonych zadań.
- Przy dużej ilości przetwarzanych dokumentów potrzebne jest narzędzie monitorujące pozwalające śledzić pracę NGinn i wykrywać ewentualne problemy. Dobrze by też było mieć jakieś narzędzie pozwalające na raportowanie przebiegu procesów. Na potrzeby tego konkretnie wdrożenia zostało uruchomione niezbyt rozbudowane narzędzie pozwalające śledzić podstawowe informacje – np aktywność w NGinn w ciągu ostatnich 24 godzin – poniżej obrazek z tego narzędzia:

Podsumowując, myślę że udało się pokazać że za pomocą NGinn można realizować rzeczywiste wdrożenia i narzędzie to potrafi wspierać procesy biznesowe nie tylko na papierze. Mam nadzieję na uruchomienie kolejnych procesów dla tego samego klienta i myślę że to pozwoli rozbudować NGinn o kolejne przydatne funkcje.
Jakie powinno być oprogramowanie dla dużych organizacji?
Ponieważ od wielu lat zajmuję się realizacją i wdrożeniami oprogramowania wspierającego procesy biznesowe w dużych organizacjach zebrało mi się sporo przemyśleń na temat specyfiki tej pracy, trudności które najczęściej wdrożeniowców czekają oraz sposobów radzenia sobie z typowymi przy takich projektach problemami. Dziś zatem będzie o tym w jaki sposób dobrze zaprojektowane oprogramowanie może pomagać nam w realizacji skomplikowanych projektów. Artykuł ten jest wyjaśnia też dlaczego projektując NGinn starałem się uwzględnić pewne cechy oprogramowania które uważam za pomocne przy realizacji potrzeb naszych klientów.
Wdrożenie
Mówiąc o wdrożeniu systemu w dużej organizacji mam na myśli firmy liczące kilka tysięcy pracowników gdzie wdrażany system ma spory ‘zasięg rażenia’, czyli obejmuje istotną część personelu firmy i w jakiś sposób dotyczy istotnych dla biznesu tej firmy procesów. Przykładem takiego oprogramowania są systemy CRM, wsparcia klientów, obsługi zamówień/zleceń lub też systemy wspierające wewnętrzne procesy w dużych firmach – HR, zakupy wewnętrzne, helpdesk IT etc. W takim środowisku wymagania najczęściej nie pochodzą od jednej osoby ale są jakąś wypadkową potrzeb i planów rozwojowych wielu osób z różnych działów danej firmy. Oznacza to że prowadząc wdrożenie musimy współpracować z wieloma ‘właścicielami biznesowymi’ jednocześnie, najczęściej będą to managerowie jednostek organizacyjnych w których wprowadzamy nasz system. Wiele źródeł wymagań oznacza potencjalne rozbieżności w specyfikacji potrzeb no i różne sposoby interpretacji tego co zostało ustalone - prawdopodobnie nie da się wszystkiego uzgodnić tak żeby specyfikacja była spójna, kompletna, niesprzeczna i na dodatek tak samo rozumiana przez wszystkie strony. Oczywiście należy do tego dążyć, ale wg mnie trzeba na jakimś etapie odpuścić i uznać że tak po prostu jest że każdy chce czego innego i na co innego zwraca uwagę. Problem jest taki że system musi być oparty na niesprzecznej wewnętrznie logice no i zapewnienie logiczności tej logiki to właśnie nasze zadanie, ale myślę że aby pracę w ogóle dało się zacząć to wystarczy ustalić kwestie podstawowe, złapać jaki-taki obraz całości i pilnować żeby każdy właściciel biznesowy na końcu dostał to na czym mu zależy. Przy takim podejściu musimy dopuścić możliwość robienia wyjątków od ustalonych przez wszystkich reguł, na przykład ustalając że system zaoferuje inną niż dla całej reszty firmy procedurę pracy lub zawartość formatki dla pracowników jakiegoś działu X bo akurat manager tego działu chce inaczej zarządzać u siebie pracą. Zamiast zmuszać wszystkich właścicieli biznesowych do uzgodnienia jednego wspólnego stanowiska w tej sprawie po prostu uwzględniamy potrzeby X w budowanym systemie tak aby nie miało to wpływu na pozostałych.
A co ze stroną techniczną przy takim podejściu? W sumie to każdy system może uwzględnić rozbieżne wymagania i wyjątki (oraz wyjątki od wyjątków) w logice biznesowej, różnice są tylko w sposobie implementacji takich wymagań, stopniu komplikacji wynikowego systemu oraz nakładzie pracy koniecznym do implementacji i późniejszego utrzymania systemu. Jest natomiast kilka cech oprogramowania które takie zadania ułatwiają:
- oddzielenie logiki biznesowej od infrastruktury. Oprogramowanie powinno w przejrzysty sposób dzielić się na infrastrukturę (czyli komponenty odpowiedzialne za podstawowe funkcje systemu które się nie zmieniają z wdrożenia na wdrożenie) oraz na ‘logikę biznesową’ czyli specyficzne dla klienta struktury danych, GUI, formularze, szablony dokumentów i reguły według których system realizuje funkcje uzgodnione z klientem. Najlepiej żeby podział na infrastrukturę i logikę był silny, tzn każdy komponent powinien być jednoznacznie przypisany do jednej z tych grup, a dodatkowo infrastruktura powinna być na tyle dobrze zaprojektowana żeby implementacja kolejnych funkcji biznesowych nie wymagała omijania ani modyfikacji infrastruktury.
- możliwość wprowadzania lokalnych zmian w logice. To szeroki temat, ale chodzi o to aby uwzględniając czyjeś specjalne potrzeby można było działać ‘lokalnie’ nie wpływając na pozostałe funkcje systemu, na przykład zaoferować dla wybranych użytkowników specjalną wersję formatki bez modyfikowania formatki ‘ogólnej’, tj tej dostępnej dla wszystkich, albo zmienić reguły rządzące przepływem określonego typu dokumentów bez modyfikowania reguł ogólnych którym podlegają wszystkie inne dokumenty. I odwrotnie - system w którym cała logika jest zapisana w jednym pliku z kodem źródłowym w postaci gigantycznego i wielokrotnie zagnieżdżonego if-else na pewno nie spełnia tej reguły i jest bardzo trudny do modyfikacji.
- zwięzły, przejrzysty i najlepiej samo-dokumentujący się sposób zapisu logiki biznesowej. Struktura musi być łatwa do zrozumienia i zobrazowania, dzięki temu będziemy w stanie zapanować nad wielością reguł i wyjątków od nich. Można taki cel osiągnąć np przez wprowadzenie specjalizowanego języka ‘wyższego poziomu’ do zapisywania logiki biznesowej, np w postaci reguł if-then-else albo procesów w określonej notacji, tudzież opartego o XML języka opisu wyglądu formatek
Zmiana
Wracając do samego wdrożenia, chyba najważniejszym problemem z którym sobie musimy poradzić jest zmiana. Zmiany w dużych organizacjach to norma, możemy być pewni że w momencie uruchomienia systemu po raz pierwszy zacznie płynąć strumyczek wniosków racjonalizatorskich, żądań zmian i pomysłów na kolejne zastosowania systemu. Dla nas powinna to być okazja do robienia biznesu a nie rzecz z którą należy walczyć – należy klienta zachęcać do zmian i je realizować, problem tylko w tym jak to zrobić żeby nie wpaść przy okazji po szyję w bagno. A bagno pojawia się kiedy na skutek radosnej twórczości klienta (przy naszym współudziale) system robi się bardzo skomplikowany, reguły niejasne, niespójne i czasami się wykluczające i pojawiają się dziwne ‘błędy’ za które jesteśmy obarczani odpowiedzialnością. A wina nie zawsze jest po naszej stronie – no bo po kilku latach rozwoju systemu w firmie sytuacja zwykle przedstawia się tak:
- nikt po stronie klienta nie ma pełnego obrazu tego jakie funkcje i w jaki sposób system realizuje. Po prostu nie ma takiej osoby bo różne wymagania pochodziły od różnych ludzi, w różnym czasie i nie ma nikogo kto by to potrafił albo chciał uzgodnić
- ludzie którzy definiowali jakieś funkcje i ustalali z nami sposób ich działania albo już nie pracują, albo nie pamiętają, albo nie chcą pamiętać, albo dostali inne zadania i już nie biorą udziału w projekcie
- nowi managerowie nie biorą odpowiedzialności za nic bo ‘to nie oni ustalali’. Mówią tylko że w systemie jest błąd bo nie pasuje do ich koncepcji.
- Dokumentacja nic nam nie pomoże, po prostu jest zbyt ogólna i fragmentaryczna. Nie sposób spisać każdego szczegółu.
- Jeśli się pilnowaliśmy to mamy rejestr wprowadzanych zmian. Świetnie, ale to nic nam nie pomoże bo jest tego tyle że trzeba nadludzkich możliwości żeby analizując poszczególne zmiany ustalić jaki powinien być aktualny stan systemu. Poza tym wiele drobiazgów było załatwiane na gębę/telefon/maila i nie ma już po tym śladu
- Może nawet mamy specyfikację systemu i wiemy co i dlaczego w nim działa tak jak działa (o, super!) ale system i tak ‘źle działa’ bo w międzyczasie u klienta była reorganizacja, np cześć procesu została ‘outsorcowana’, wprowadzono nowe grupy, inne zasady rozliczeń itp itd. Oczywiście to nasz problem a nie klienta, no bo przecież wymagania mówią że system ma realizować jakiś tam cel a nagle przestał.
Właściwie wszystkie powyższe punkty przerabiałem, myślę że każdy informatyk który pracuje z klientami również byłby gotów się z większością zgodzić. I myślę też że nie ma skutecznego sposobu który by pozwalał sobie z tym poradzić. Ale trzeba zacisnąć zęby i z tym żyć (albo zmienić pracę na jakąś mniej stresującą), dobrze też jest mieć jakieś oparcie w oprogramowaniu – a oto cechy które uważam za bardzo pożądane w takiej sytuacji:
- Wszystkie 3 punkty o których pisałem wyżej przy okazji specyfikacji wymagań, oraz:
- Możliwość współistnienia kilku wersji logiki biznesowej. Na przykład jednoczesnego działania w oparciu o starą logikę i nową logikę – po prostu, stare sprawy biegną wg starych reguł, nowo zakładane – wg nowych. To bardzo ułatwia migracje. To pozwala też na robienie zmian lokalnych o których pisałem wcześniej – czyli takich co obejmują tylko niektórych użytkowników nie wprowadzając zamieszania u pozostałych. No i ułatwia ewentualny rollback do poprzednich wersji jeśli nowa nie okaże się takim sukcesem jak się wydawało.
- Samo-dokumentacja (również wspomniany wcześniej). Logika biznesowa powinna być zapisana tak, żeby łatwo można było ustalić aktualny sposób działania systemu. Na przykład strukturalny zapis w postaci reguł if-then-else pozwala nam narysować drzewo decyzyjne wg którego system podejmuje jakieś działania, a np zapis procesów biznesowych w jakiejś strukturalnej notacji pozwala uzyskać diagram aktualnego przepływu dokumentów w firmie.
- Możliwość wprowadzania zmian w locie. To jest istotne bo duże organizacje często pracują 24×7 i system jest tak samo potrzebny zarówno w dzień jak i w nocy. Nie można sobie pozwolić na długie przestoje, zwłaszcza kiedy zmian jest dużo i są wprowadzane bardzo często, nawet ad-hoc gdy ktoś zaobserwuje że system działa niezgodnie z oczekiwaniami i trzeba to szybko naprawić.
- Możliwość wprowadzania zmian lokalnie, bez zatrzymywania pozostałych komponentów systemu – trochę związane z poprzednim, ale chodzi o to aby do wprowadzenia zmian w jakiejś funkcji systemu nie trzeba było wyłączać całego systemu a zasięg naszych działań był tak mały jak to tylko możliwe.
- Cała logika biznesowa powinna być przechowywana w postaci plików tekstowych. Dlatego że w ten sposób łatwo śledzić zmiany np za pomocą systemu kontroli wersji. Wszelkie formaty binarne (np z graficznych designerów), bloby w bazie danych, bardzo utrudniają pracę.
- Strukturalny podział logiki biznesowej według funkcji realizowanych przez system a nie czegoś innego (chociażby np obiektów w bazie relacyjnej). Biznesowo widziane funkcje systemu mają charakter przekrojowy – obejmują wiele fragmentów aplikacji, takich jak struktura danych w bazie, GUI, uprawnienia, specyficzną dla danej funkcji logikę, powiadomienia email, szablony dokumentów, raporty itp itd. Wszystko to może składać się na jedną logiczną funkcję, o nazwie np ‘obsługa płatności’ albo ‘obsługa komunikacji z dostawcami’. Dobrze by było żeby struktura systemu grupowała te elementy w pakiety według logicznych funkcji, wtedy łatwo ustalić które elementy systemu biorą udział w realizacji określonych wymagań i jaki będzie zakre zmian w systemie przy modyfikacji określonej funkcji. Istotna jest też separacja – dwie różne funkcje raczej nie powinny wykorzystywać tych samych fragmentów logiki biznesowej czy GUI, nawet jeśli są one bardzo podobne. A to dlatego że wprowadzając zmiany dla jednej grupy użytkowników nie chcemy przy okazji psuć czegoś pozostałym. ‘Code reuse’ to dobra rzecz ale nie tutaj.
- Preferuję systemy ‘miękkie’ – tzn skryptowe, interpretowane, dynamiczne – w przeciwieństwie do statycznie typowanych, kompilowanych, buildowanych, package’owanych i deployowanych. Chodzi o modyfikacje logiki biznesowej, konfiguracji, uprawnień czy GUI. Infrastruktura może być kompilowana i niezmienialna po wdrożeniu, ważne aby logika była modyfikowalna. Dzięki temu możemy dokonywać szybkich poprawek, mini-wdrożeń, szybkich testów nowej funkcjonalności itp. Generalnie uważam że logika biznesowa powinna być zapisywana w językach skryptowych (przy czym wybieramy takie języki które najlepiej się nadają do określonego celu), zaś infrastruktura w językach niższego poziomu – C#, C++, Java itp.
- Cechy powyższe pozwalają nam też zmienić podejście do testowania i prototypowania nowych funkcji. Mając możliwość wrzucenia nowej wersji logiki bez wyłączania starej możemy przeprowadzać testy nowych czy zmodyfikowanych funkcji na produkcji – po prostu udostępniając je wybranej grupie testerów. Po potwierdzeniu poprawnego działania włączamy funkcję dla wszystkich użytkowników. Bardzo często testy obejmują nie tylko zmiany w systemie, ale również zmiany w organizacji – klient po prostu chce realizować nowy proces posługując się naszym narzędziem i chce go sobie przetestować na jakiejś małej grupie ludzi. W takim przypadku testowanie na produkcji jest czymś naturalnym i bardzo ułatwia późniejsze uruchomienie ‘oficjalne’. Jest to też zachęta dla klienta do eksperymentowania – zwykle sprawdzenie czegoś ‘na żywo’ jest lepsze, szybsze i tańsze niż pisanie dokumentów z wymaganiami i analizowanie tematu ‘na sucho’.
Wydajność
Sprawa wydajności jest bardzo ważna – możemy dowolnie rozwijać i modyfikować nasz system, ale musi on zapewniać wydajność wystarczającą do obsłużenia wszystkich nowych pomysłów. A jeśli odnieśliśmy sukces to klient będzie chciał objąć wdrożeniem jakieś nowe obszary organizacji co oznacza więcej danych, więcej użytkowników no i więcej funkcji w aplikacji. W niektórych przypadkach rozwój systemu jest tak szybki że przerasta to nasze oczekiwania oraz możliwości systemu który nie był projektowany do tak dużego obciążenia. W tym miejscu kilka uwag na temat potencjalnych ‘min’ wydajnościowych:
Uwaga na relacyjne bazy danych. Mimo że relacyjna baza danych to podstawa bez której nikt nie wyobraża sobie aplikacji biznesowej to jednak jest to narzędzie które ma swoje ograniczenia. Po pierwsze jest ’sztywna’ – wymaga uzgodnienia struktur danych i trzymania się ich. Tabela w bazie jest taka sama dla wszystkich przechowywanych w niej obiektów, nawet jeśli różnią się one od siebie na poziomie aplikacji. Bazy danych nie obsługują dziedziczenia. Nie można wprowadzać zmian lokalnie – albo zmieniasz całą tabelę albo nic, zmiana struktury danych dotyczy wszystkich miejsc systemu które się do niej odwołują. Druga sprawa to wielkość bazy danych – jeśli chcesz wprowadzić zmiany w tabeli która liczy sobie miliony rekordów licz się z tym że może to być bardzo długotrwały proces albo nawet i niewykonalny (!). Na przykład dodanie nowej kolumny do rekordu aktualizuje nam wszystkie stare rekordy które już są w bazie – rozmiar rekordu się zwiększa i następuje reorganizacja całej tabeli która może trwać nawet kilka godzin. A nasz czas na serwis to 30 minut… niby prosta zmiana a nie do zrobienia. Trzecia sprawa to wydajność samej bazy – rozwój systemu to coraz większa jego komplikacja i coraz bardziej złożone transakcje w bazie danych. Transakcyjność to bardzo fajna rzecz ale potrafi również mocno ograniczyć wydajność aplikacji poprzez nakładanie blokad wykluczających współbieżne wykonywanie operacji. Na dodatek nie bardzo można zwiększać wydajność systemu przez dołożenie kolejnej bazy danych – najczęściej baza musi być jedna no bo chcemy działać na jednej, aktualnej wersji danych a nie na kilku niezsynchronizowanych kopiach. Dobrze jest przemyśleć te rzeczy na jakimś wczesnym etapie bo im więcej danych się nagromadzi tym mniejsze mamy możliwości manewru.
Kolejna sprawa to komunikacja między komponentami systemu oraz między systemem a innymi aplikacjami z którymi jest zintegrowany. Należy uważać na komunikację synchroniczną – mimo że wydaje się fajnym pomysłem na początku to może nam mocno obciąć wydajność całej aplikacji. Kluczowe operacje w systemie należy rozbić na jak najmniejsze kawałki, tak aby transakcje miały niewielki zasięg i wykonywały się szybko. Resztę operacji można dokończyć asynchronicznie. Przykład: podczas rejestracji nowego dokumentu przez użytkownika pozyskujemy wymagane dane od użytkownika i rejestrujemy dokument w bazie. Wszelkie operacje dodatkowe, takie jak powiadomienie o nowym dokumencie innych systemów, wysłanie emaili, indeksowanie dokumentu itp robimy później, offline, po zakończeniu interakcji z użytkownikiem i zamknięciu tej pierwszej transakcji w bazie danych. Komunikację z systemami zewnętrznymi robimy w tle bo nigdy nie wiadomo jak długo przyjdzie nam czekać na odpowiedź. Tak samo jeśli oferujemy jakąś usługę (np web service) dla systemów zewnętrznych – gdy zrobimy to w wersji synchronicznej może się okazać że zewnętrzne aplikacje po prostu ‘zarzynają’ nasz system. Bardzo dobrym pomysłem jest wykorzystanie architektury opartej o ‘message bus’ tj mechanizm asynchronicznego przekazywania komunikatów między komponentami systemu – w takiej architekturze naturalne jest podejście asynchroniczne. Temat jest godny jakiegoś rozwinięcia, ale to kiedy indziej.
NGinn
Oczywiście nie mogę tutaj pominąć NGinn – jest to narzędzie które projektowałem biorąc pod uwagę wszystko co napisałem wyżej (i pewnie jeszcze trochę spraw o których nie napisałem). NGinn jest moją odpowiedzią na problem realizacji skomplikowanych i ciągle zmiennych wymagań w rozbudowanych systemach. Oczywiście jeden NGinn nie załatwi wszystkiego, jest to tylko workflow engine i nie obejmuje na przykład GUI systemu, ale skutecznie stosuje opisane tu reguły do realizacji logiki biznesowej systemu. A to dlatego że:
- Proces w NGinn opisuje nam logikę systemu potrzebną do realizacji określonego procesu biznesowego, tj zawiera opis konkretnej funkcji biznesowej systemu, od początku do końca. Procesy grupowane są w pakiety które oprócz samej definicji procesu mogą przenosić dodatkowe elementy potrzebne do realizacji danej funkcji biznesowej – np szablony powiadomień, logikę integracyjną, etc
- NGinn obsługuje wersjonowanie procesów – procesy mogą współistnieć w wielu wersjach jednocześnie, wprowadzanie nowej wersji procesu nie wymaga wyłączenia starej ani migracji
- Można modyfikować istniejące procesy bez zatrzymywania systemu.
- Struktury danych w NGinn są dynamiczne – modyfikując proces można zmieniać definicję jego danych, nie pociąga to za sobą konieczności modyfikacji struktury bazy danych lub aktualizacji innych procesów które już się toczą.
- Asynchroniczny w założeniach – NGinn polega głównie na asynchronicznej komunikacji między komponentami oraz między NGinn a światem zewnętrznym. Wewnętrznie NGinn używa kolejki komunikatów do przesyłania informacji pomiędzy procesami.
- Definicja procesu jest jednocześnie dokumentacją logiki systemu – mając definicję można wygenerować graficzny schemat procesu.
- NGinn pozwala używać ‘rules engine’ do zapisu drzew decyzyjnych w postaci reguł if-then-else. Może się to przydać tam gdzie istnieje potrzeba podejmowania różnych działań w oparciu o rozmaite atrybuty danych wejściowych.
- W jasny sposób oddzielona jest infrastruktura NGinn od logiki biznesowej. Logika biznesowa jest przechowywana w definicji procesu w oparciu o którą infrastruktura ten proces realizuje.
Na koniec…
Podsumowując to wszystko, myślę że najefektywniejszą techniką tworzenia oprogramowania na zamówienie dla wymagających klientów (hm, czy są klienci niewymagający?) jest szybkie prototypowanie wsparte odpowiednio dostosowanym narzędziem. Pomaga to zarówno przy ustalaniu wymagań jak i przy późniejszej ich realizacji. Klientom bardzo przydaje się możliwość zobaczenia tego co chcą zamówić i skonfrontowania swoich wyobrażeń z rzeczywistością, a my dzięki temu mamy wymagania lepszej jakości i bardziej odpowiadające możliwościom systemu. No i szybciej możemy wystawić fakturę.
Oczywiście nie jest to jakieś rewolucyjne podejście – w świecie informatyki funkcjonują rozmaite metodyki prowadzenia projektów mające w założeniach szybkie prototypowanie – na przykład Extreme Programming czy rozmaite Agile – ale zostawmy ten temat, w końcu nie zajmujemy się tu teorią tylko robieniem softu. Reprezentuję punkt widzenia gościa który własnymi (lub zespołu) rękami tworzy oprogramowanie a własną głową odpowiada za dostarczenie go klientowi zgodnie z zamówieniem i w terminie i mam nadzieję że takie spojrzenie od strony warsztatu jest dobrym uzupełnieniem ‘best practices’ prowadzenia projektów informatycznych.
Wyszukiwanie danych w procesach
Dziś zajmę się sprawą wyszukiwania danych procesu w NGinn. Problemem w tym przypadku jest sposób przechowywania danych procesu przez NGinn – otóż dane są przechowywane jako fragment dokumentu JSON zawierającego stan procesu. Taki sposób przechowywania danych nie ułatwia ich przeszukiwania – nawet jeśli dokumenty są zapisywane w relacyjnej bazie danych to i tak nie da się za pomocą SQL zadawać zapytań o poszczególne pola w dokumencie.
Rozwiązaniem jest odpowiednie indeksowanie dokumentów, przy czym nie mam tu na myśli indeksów znanych z relacyjnych baz danych a zewnętrzny indeks pełnotekstowy pozwalający na umieszczanie w nim dowolnych danych. Jednym z narzędzi tego typu jest Apache Lucene oraz jego port do środowiska .Net pod nazwą Lucene.Net. Lucene pozwala na budowę indeksów o dowolnej zawartości (każdy wpis w indeksie może zawierać dowolny zestaw pól) które potem mogą być przeszukiwane za pomocą prostych zapytań.
Poświęciłem ostatnio trochę czasu na próbę połączenia NGinn i Lucene i wyniki są bardzo zachęcające. Przede wszystkim wrażenie robi wydajność Lucene.Net – wyniki są zwracane w mgnieniu oka a mechanizm wyszukiwania jest bardzo sprytny i ma wiele interesujących możliwości. Wystarczy w aplikacji umieścić jedno pole wyszukiwania i możemy szukać po dowolnych danych w procesie a nawet konstruować bardziej zaawansowane zapytania zbudowane z kilku predykatów połączonych operatorami logicznymi. Dodatkowo Lucene w naturalny sposób obsługuje paging co znacznie ułatwia budowę aplikacji. Nie będę tu wchodził w techniczne szczegóły, ale mechanizm indeksowania danych NGinn za pomocą Lucene działa w trybie offline, tzn indeksuje dane z pewnym niewielkim opóźnieniem dzięki czemu indeksowanie nie ma negatywnego wpływu na szybkość działania NGinn. Komponent indeksujący uaktywnia się tylko po zmianie stanu jakiegoś procesu i wtedy wpis w indeksie dla tego procesu jest aktualizowany. Nie robiłem testów na milionach rekordów, ale przy właściwym poindeksowaniu danych wyszukiwanie za pomocą Lucene powinno być o wiele szybsze niż jakiekolwiek zapytania SQLowe. Jeśli chodzi o wielkość indeksu to jest ona zaskakująco mała biorąc pod uwagę ilość danych którą można w nim umieścić.
Jeśli chodzi o plany rozwojowe to w najbliższym czasie nie zamierzam umieszczać w NGinn komponentu indeksującego – jest to fajny dodatek ale ponieważ nie jest bezpośrednio związany z funkcjami workflow engine to na razie nie będę go rozwijał w ramach projektu Open Source – no chyba że pojawi się perspektywa jakiegoś interesującego wdrożenia.
Poniżej zamieszczam przykładowy ekran wyszukiwania z prostej aplikacji którą posiłkuję się przy pracach nad NGinn. Łącząc framework Ext JS oraz Lucene udało się bardzo szybko rozbudować tę aplikację o funkcję wyszukiwania procesów na podstawie ich wewnętrznych danych. Tak więc szukającym dobrego search engine dla swojego systemu bardzo polecam Lucene.

O dokumentach, procesach, bazach danych i JSON. No i o przyszłości.

Mówiąc o jakichkolwiek procesach biznesowych wcześniej czy później trafiamy na pojęcie dokumentu. To wokół dokumentu buduje się procesy – dokumentem może być jakiś papier wędrujący przez organizację, faktura, sprawa zarejestrowana w jakimś systemie, nadesłany przez klienta email z zażaleniami i tak dalej. Jeśli chcemy taki proces ‘zinformatyzować’ to również dokumenty muszą zostać w jakiś sposób przetworzone na postać cyfrową i zarejestrowane w jakimś systemie.
W przypadku NGinn nastawiłem się na maksymalną elastyczność w temacie dokumentów do których odnoszą się procesy – mianowicie elastyczność wynika z całkowitego pominięcia tego tematu. Procesy w NGinn operują na danych (zostało to opisane we wcześniejszych artykułach) i te dane zawierają wszystkie informacje istotne z punktu widzenia działania procesu. W szczególności założyłem że będą mogły zawierać odnośnik do odpowiedniego dokumentu w systemie zewnętrznym – na przykład numer faktury w systemie księgowym albo numer zgłoszenia w systemie wsparcia klientów. Sam system NGinn nie wnika w to w jakim systemie i w jaki sposób są te dokumenty przechowywane, natomiast oferuje mechanizmy integracyjne pozwalające na wymianę danych z takimi systemami i pobieranie odpowiednich informacji o dokumencie jeśli to konieczne. To pozwala twierdzić że NGinn znakomicie nadaje się do rozszerzania możliwości istniejących w firmie systemów oraz do ich integrowania, ale zupełnie pomija przypadek gdy odpowiedniego systemu nie ma w firmie a dokumenty dopiero czekają na bazę danych która je przechowa. No właśnie, co wtedy?
Pierwszy pomysł to wykorzystanie danych procesu. NGinn pozwala na definiowanie własnych struktur danych dla procesów, więc nic nie stoi na przeszkodzie żeby danymi procesu był cały dokument. Projektujemy strukturę danych tak żeby uwzględnić wszystkie pola dokumentu i wtedy zawartość naszego ‘dokumentu’ jest przechowywana przez NGinn razem z innymi informacjami o procesie. Zaletą jest to że nie musimy się martwić o przechowywanie tych danych, wad za to jest kilka – przede wszystkim dokument istnieje tylko razem z procesem i ‘zamiera’ gdy proces się kończy, dodatkowo NGinn bardzo ogranicza to co można zrobić z danymi procesu. Tak więc ten sposób jest dobry tylko dla prostych struktur danych, nieistotnych poza procesem który ich dotyczy.
Druga możliwość wiąże się z pewną rozbudową NGinn. Otóż w tej chwili NGinn przechowuje stan procesów i zadań w postaci JSON. Każde zadanie zapisywane jest w postaci pojedynczego dokumentu JSON zawieracjącego wszystkie jego dane i informacje potrzebne do działania procesu. Dzięki temu mamy elastyczność i dowolność w definiowaniu struktury procesu i jego danych. No i jeśli byśmy chcieli rozszerzyć NGinn o bazę dokumentów to możemy po prostu wykorzystać ten sam mechanizm i przechowywać dowolne dokumenty w postaci JSON – wtedy zarówno dokument jak i proces toczący się wokół niego były by obsługiwane w ten sam sposób, jako dokumenty JSON. Pomysł interesujący według mnie, ale czy są z nim jakieś problemy?
Są, ale to raczej ‘wyzwania’ niż problemy. Chodzi o to że w tej chwili do przechowywania dokumentów JSON ze stanem procesu jest wykorzystywana baza danych (MSSQL) – jest tam jedna tabela z polem tekstowym (ntext) do której wrzucany jest cały dokument JSON. Dla NGinn to jest wystarczające, ale gdybyśmy w ten sposób chcieli przechowywać dokumenty z danymi które chcemy przeszukiwać to szybko się okaże że szukanie odpada, nie da się efektywnie takiej postaci danych przeszukiwać a i aktualizacja jest mocno ograniczona. Rozwiązaniem według mnie jest przejście na nierelacyjną bazę danych, na przykład taką przeznaczoną do obsługi dokumentów JSON. Takich baz danych jest bardzo wiele, komercyjnych i darmowych. Powstały jako ‘back-end’ dla serwisów internetowych obsługujących bardzo duży ruch i wymagacjących ogromnej skalowalności bazy danych – np bardzo popularnych serwisów społecznościwych i sprawdzają się w takich zastosowaniach o wiele lepiej niż bazy relacyjne. Dodatkowo są bardzo elastyczne jeśli chodzi o struktury danych, a to jest wg mnie kluczowa sprawa przy obsłudze dużych (raczej bardzo dużych) ilości dokumentów. Wystarczy pomyśleć co się dzieje gdy chcemy np zmienić strukturę istniejącej bazy relacyjnej:
- czas: jeśli tabela jest bardzo duża (miliony rekordów) to dodanie nowej kolumny może trwać bardzo długo gdyż może wywołać zwiększenie rozmiaru rekordu i reorganizację całego pliku bazy danych. Długo to znaczy np 5 godzin, gdy nasze ‘okienko serwisowe’ to 30 minut.
- dostępność: na czas aktualizacji bazy relacyjnej musimy wyłączyć aplikację gdyż operacje modyfikacji bazy wykluczają jej odczyt (blokada dostępu do tabel na czas aktualizacji)
- inne problemy: np konieczność zdejmowania i ponownego zakładania constraintów przy operacjach na danych, czasami prosta aktualizacja wymaga niezmiernie skomplikowanych operacji na więzach integralności i indeksach
- no właśnie, indeksowanie, nie można z nim przesadzić gdyż nadmierne poindeksowanie danych spowalnia ich aktualizację
W przypadku bazy dokumentów JSON nie musimy w żaden sposób zmieniać jej struktury gdy modyfikujemy strukturę naszych dokumentów, zaś indeksowanie jest w trybie ‘offline’ czyli nie wpływa na wydajność aktualizacji. Dodając do tego fakt że JSON jest podstawowym formatem dokumentu w NGinn użycie tekstowej bazy dokumentów wydaje się naturalnym pomysłem. A dobrym kandydatem wydaje mi się baza ‘Persevere’ (http://www.persvr.org/) – jest to kompletna baza danych dokumentów JSON z dostępem poprzez HTTP/REST i z rozbudowanymi funkcjami ‘bazodanowymi’ takimi jak indeksowanie, przeszukiwanie czy zarządzanie zawartością bazy. Polecam zapoznanie się choć pobieżnie z jego możliwościami oraz potencjalnymi zastosowaniami, wg mnie to bardzo otwiera oczy na możliwości tych ‘nowych’ baz danych oraz ich potencjalne zastosowania. Jak by wyglądał NGinn po przejściu na bazę Persevere (lub inną tego typu)? Przede wszystkim myślę że wtedy nie będzie używał żadnej bazy relacyjnej, a po drugie myślę że byłby wydajniejszy i bardziej elastyczny, zwłaszcza gdyby dać możliwość dowolnego indeksowania dokumentów i danych w procesach w celu ich łatwego przeszukiwania. Na pewno poświęcę temu niedługo więcej czasu.
P.S. Bazy w rodzaju Persevere zwykle nie obsługują transakcji rozproszonych ani two-phase commit. To jest duże ograniczenie dla NGinn i główny problem do przezwyciężenia. Chodzi o to że w tej chwili NGinn wykorzystuje transakcje rozproszone (transactionscope) do zapewnienia spójności stanu procesu w razie jakichś problemów.
Krótki update
W czerwcu nie było żadnego wpisu o NGinn, a to dlatego że mam ostatnio mało czasu. Z powodów różnych w tej chwili przechodzę z pracy etatowej na działalność gospodarczą i musialem odłożyć nieco aktywność poza-zawodową. Ale w związku z rozpoczęciem pracy na własny rachunek planuję z NGinn uczynić projekt ‘profesjonalny’ co powinno dobrze wpłynąć na jego rozwój. Więcej informacji niedługo.
Operacja na żywym organiźmie czyli wprowadzanie zmian w procesach
Jeśli zajmujemy się tworzeniem oprogramowania na zamówienie, zwłaszcza oprogramowania używanego do obsługi jakichś procesów w firmach, wcześniej czy później stajemy przed koniecznością dokonywania zmian w istniejących funkcjach systemu. Największe problemy w takich przypadkach wynikają z niekompatybilnością wstecz – gdy nowa logika systemu jest istotnie różna od tej która była wcześniej, a dane zebrane w aplikacji do tej pory mają ’starą’ strukturę i trzeba je jakoś zaktualizować aby system mógł działać dalej po wdrożeniu zmian. Jeśli aplikacja jest intensywnie modyfikowana a nie zapewnia jakiegoś sensownego podejścia do aktualizacji to raczej szybko doprowadzimy do koszmarnej sytuacji kiedy 5% czasu poświęcamy na implementację zmiany, a pozostałe 95% na migrację istniejących danych oraz obsługę wygenerowanych przy tej okazji problemów. Dziś spróbuję podpowiedzeć jak wykorzystując NGinn można sobie ułatwić przyszły rozwój systemu.
W przypadku NGinn mówimy o aktualizacji procesów. Załóżmy że działamy w firmie i mamy zaimplementowany proces który jest w użyciu od dłuższego czasu – to oznacza że mamy mnóstwo spraw obsługiwanych tym procesem, część z tych spraw jest zakończona a część jest w trakcie realizacji na różnych etapach. Jeśli tak po prostu zmodyfikujemy definicję tego procesu to prawdopodobnie spowodujemy że toczące się sprawy staną się niekompatybilne z nową definicją i z powodu błędów nie będą mogły się zakończyć. Tego bardzo nie chcemy, bo jeśli to my dostarczamy oprogramowanie to po takim wdrożeniu zostaniemy zmuszeni do posprzątania całego bałaganu. O tej strategii nie będę więcej pisał, wszyscy wiedzą że można jej użyć w każdym przypadku ale lepiej wiedzieć co się robi. Przyjrzyjmy się lepiej innym sposobom:
1. Wersjonowanie procesu
NGinn pozwala używać kilku wersji definicji tego samego procesu (w tym celu jest numerek wersji w nazwie pliku z definicją), więc w wielu przypadkach najprościej jest zdefiniować nową wersję procesu która będzie używana dla spraw które dopiero zostaną zarejestrowane. Aktualnie toczące się sprawy pozostają na starej definicji procesu i mogą spokojnie się zakończyć ‘po staremu’. Unikamy w ten sposób jakiejkolwiek migracji danych więc jeśli uda się klienta przekonać do takiego rozwiązania sprawy to mamy sukces gwarantowany.
2. Anulowanie i ponowne uruchomienie
W niektórych przypadkach można rozwiązać problem aktualizacji poprzez anulowanie aktualnie toczących się spraw i ponowne ich uruchomienie już na nowej definicji procesu. Jak to zrobić w NGinn? Ano, narzędzia do tego nie ma, ale jest API pozwalające anulować procesy i je uruchamiać oraz ‘grzebać’ w danych procesów i zadań. Zatem można stworzyć sobie narzędzie które nam taką migrację obsłuży. Problem może wystąpić jeśli do uruchomienia procesu w nowej wersji konieczne są inne dane wejściowe – wtedy trzeba też dokonać konwersji i ewentualnego uzupełnienia danych. Istotna rzecz: NGinn nie zapamiętuje oryginalnych wartości danych wejściowych procesu – pamięta tylko ich aktualne wartości. Zatem jeśli podczas działania procesu zmienne wejściowe są modyfikowane to może się okazać że stracimy informacje potrzebne do uruchomienia tego samego procesu w nowej wersji. Taka sytuacja jednak jest dość rzadka, zwykle dane wejściowe istotne dla sprawy nie zmieniają się w trakcie jej realizacji.
3. Użycie Barier
Jeśli koniecznie chcemy aktualizować toczące się sprawy ‘na żywca’, to bardzo nam przeszkadza fakt że znajdują się one na rozmaitych etapach realizacji i trzeba to uwzględnić wprowadzając poprawkę – po prostu musi być ścieżka migracji dla każdego etapu na którym może się znajdować sprawa. W niektórych przypadkach nie trzeba nic robić – na przykład jeśli sprawa znajduje się na początku procesu, a my modyfikujemy jego końcówkę to nic złego się nie wydarzy. Zatem jeśli mieli byśmy taką sytuację że wszystkie sprawy znajdują się poza regionem procesu który modyfikujemy – tzn albo jeszcze do tego regionu nie doszły, albo już go opuściły – to zwykle migracja była by o wiele łatwiejsza albo wręcz niepotrzebna.
Żeby stworzyć taką możliwość w NGinn została wprowadzona funkcja “barier”. Bariera zatrzymuje nam działanie procesów w określonym miejscu i pozwala je wznowić po usunięciu bariery. Barierę stawiamy w wybranym miejscu, bądź miejscach, w procesie (chodzi o te kółeczka, czyli places).Jeśli w danym miejscu postawiona jest bariera to tokeny z tego miejsca nie mogą iść dalej, czyli nie jest uruchamiane zadanie wychodzące z tego miejsca. Tokeny czekają na usunięcie bariery czyli cała sprawa jest wstrzymana, natomiast sprawy które zdążyły pójść dalej zanim postawiliśmy barierę kontynuują proces bez przeszkód.
Jak tego użyć? Stawiamy barierę przed regionem w procesie który modyfikujemy i czekamy aż wszystkie sprawy opuszczą ten region (czyli tokeny we wszystkich instancjach tego procesu znajdą się poza modyfikowanym regionem). Bariera gwarantuje że żadne nowe tokeny nie napłyną więc oczyszczenie naszego pola działania jest tylko kwestią czasu. Wtedy zmieniamy definicję procesu i zdejmujemy barierę – wypuszczone sprawy potoczą się już wg zaktualizowanej definicji.
Czy to znaczy że właśnie opisałem stuprocentowo skuteczny sposób na aktualizację logiki systemu? No nie do końca… Przede wszystkim nie było mowy o danych. Co z tego że zaktualizujemy definicję procesu, kiedy w nowej wersji będą potrzebne dodatkowe dane (których przecież nie ma w aktualnie toczących się sprawach)? Nie będziemy w stanie ruszyć do przodu bez uzupełnienia tych danych. Do uzupełnienia czy modyfikacji danych musimy jednak zbudować własne narzędzie – NGinn daje do tego API, ale to jakie zmiany wprowadzić w danych musimy wymyślić sami. Bariera może się też tutaj przydać – jeśli sobie ’spiętrzymy’ sprawy w określonym miejscu to przynajmniej będziemy wiedzieli w których zadaniach należy zaktualizować dane.
Definicja procesu – podstawowe komponenty (1)
Definicja procesu w NGinn jest oparta na sieci Petriego – tutaj można trochę o niej poczytać. Zatem mamy trzy podstawowe elementy konstrukcyjne sieci Petriego:
- miejsca (place) – na diagramie przedstawione jako kółeczka
W procesach rozróżniamy trzy podstawowe rodzaje miejsc: miejsce startowe (w którym proces bądź podproces się zaczyna), miejsce pośrednie (czyli takie wewnątrz procesu) oraz miejsce końcowe w którym proces się kończy. Proces może mieć tylko jedno miejsce startowe i dowolną ilość miejsc pośrednich i końcowych. Miejsca końcowe nie mogą mieć połączeń wychodzących.
Podczas wykonania procesu miejsca przechowują tokeny (żetony) – czyli takie znaczniki wędrujące przez nasz model procesu pokazujące w którym miejscu proces się znajduje, czyli jaki jest jego aktualny status. W momencie wykonania zadania tokeny z jego miejsc wejściowych są zabierane, a pojawiają się nowe tokeny w miejscach docelowych tego zadania. - tranzycje – w naszej nomenklaturze znane jako zadania (task) – na diagramie rysowane w formie prostokątów:
Zadania są podstawowym budulcem procesu – odpowiadają one za wykonanie określonych akcji. W NGinn występuje kilkanaście rodzajów zadań, odpowiedzialnych np za: wysyłkę i odbieranie powiadomień, komunikację z aplikacjami zewnętrznymi, zlecanie zadań użytkownikom, odliczanie czasu itd. Dokładniejsze omówienie zadań pojawi się w kolejnych odcinkach.
Zadanie może zostać uruchomione wyłącznie jeśli posiada odpowiednią liczbę tokenów na wejściu (czyli w miejscach z których prowadzą strzałki do tego zadania). Jeśli zadanie się wykona, zjada tokeny wejściowe i produkuje tokeny na wyjściu (czyli w miejscach do których prowadzą strzałki z tego zadania). Liczba wymaganych tokenów na wejściu oraz tych produkowanych na wyjściu zależą od rodzaju synchronizacji (join) lub rozwidlenia (split) skonfigurowanego dla tego zadania (to też będzie dokładniej omówione w kolejnych odcinkach). W oryginalnej definicji sieci Petriego strzałki mogą łączyć wyłącznie miejsce z zadaniem (czyli na jednym końcu musi być miejsce a na drugim zadanie). W NGinn dozwolone są również połączenia zadanie – zadanie, w takim przypadku aby zachować ‘kompatybilność’ z sieciami Petriego system wstawia pomiędzy tak połączone zadania niejawne miejsce (nie jest ono rysowane na diagramach). - krawędzie – u nas znane jako połączenia albo przejścia albo strzałki (flow). Na diagramie występują w formie strzałek łączących miejsca i zadania:

Połączenia określają nam zależności między zadaniami w procesie. Jeśli połączenia prowadzą od miejsca do zadania, są to tzw połączenia wejściowe zadania. Określają one w których miejscach muszą pojawić się żetony aby zadanie mogło zostać uruchomione. Połączenia prowadzące od zadania do miejsca albo do innego zadania to połączenia wyjściowe – określają one miejsca w których zostaną wyprodukowane tokeny po zakończeniu zadania. Czyli połączenia definiują nam możliwe ścieżki wędrówki tokenów od miejsca startowego aż do miejsc końcowych.
W kolejnych odcinkach przyjrzymy się sposobom łączenia miejsc i zadań tak, aby uzyskać pożądane przebiegi procesów.
Obsługa danych w NGinn
Można powiedzieć że definicja procesu składa się z dwóch części. Pierwszą częścią jest jego logika, czyli zadania wchodzące w skład procesu i powiązania między nimi. Drugą częścią są dane – czyli proces = logika + dane. Jak wygląda obsługa danych w NGinn?
Zacznijmy od podstaw. Co to są te ‘dane’? W naszym przypadku są to informacje na których operuje proces i zadania wchodzące w jego skład. Aby uruchomić jakikolwiek proces w NGinn należy przekazać mu odpowiednie dane wejściowe. Następnie proces się wykonuje, przetwarza te dane i po zakończeniu może nam zwrócić rezultaty, czyli dane wyjściowe. Czyli w ‘życiu’ procesu są dwa momenty wymiany danych – start i zakończenie – i nie interesuje nas co z danymi dzieje się pomiędzy nimi.
A co z zadaniami? Aby zachować przejrzystość projektu, z zadaniami jest dokładnie tak samo. Aby uruchomić zadanie należy mu przekazać dane wejściowe, po zakończeniu zwróci nam dane wyjściowe. Praktyczny efekt jest taki, że interfejs wymiany danych z zadaniami i z procesami jest dokładnie taki sam dla wszystkich rodzajów zadań i z naszego punktu widzenia zadania w procesie można dowolnie podmieniać o ile mają taką samą strukturę danych we/wy. Tak samo w miejsce zadania można wstawić inny proces. Czyli można powiedzieć że wyłącznie struktura danych wejściowych i wyjściowych określa nam interfejsy między zadaniami.
Są też dalsze implikacje przyjętej struktury. Skoro dane wymieniamy tylko przy starcie i zakończeniu zadania, to znaczy że zadanie do swojego działania nie może wymagać innych danych niż te które otrzymało na wejściu. Po prostu nie ma innej możliwości otrzymania danych i może korzystać wyłącznie ze swoich danych wejściowych. Nie muszę chyba dodawać co to oznacza: nie ma żadnego współdzielenia danych między zadaniami – każde z nich ma swoje własne dane i nie może wpływać na dane w innych zadaniach ani w nadrzędnym procesie.
A jak to wygląda praktycznie?
Przede wszystkim, dane reprezentujemy w postaci zmiennych. Czyli struktura danych dla zadania lub procesu to zestaw zmiennych.
Każda zmienna ma przypisany kierunek wymiany danych: są zmienne wejściowe (In), wyjściowe (Out), dwukierunkowe (InOut) oraz lokalne (Local). Zmienne lokalne nie są brane pod uwagę przy wymianie danych ze światem zewnętrznym, ale spełniają tę samą funkcję co zmienne lokalne w klasycznym programowaniu – tymczasowo przechowują dane.
Oprócz kierunku zmienna ma nazwę (unikalną w ramach zadania/procesu do którego należy) oraz typ.
Wśród typów danych mamy proste typy wbudowane (tzn takie których nie trzeba definiować):
- string
- int
- bool
- DateTime
- double
oraz typy złożone, definiowane na potrzeby konkretnych procesów:
- typy wyliczeniowe (enum) – możemy tworzyć własne, nazwane enumeracje
- struktury (rekordy), czyli zestawy pól typu nazwa->wartość – odpowiednik ’struct’ w językach programowania. Pola w rekordzie mogą być dowolnego typu spośród opisanych tutaj, w szczególności mogą być rekordem – czyli definicje typów mogą być rekurencyjne
No dobra, to czego nam jeszcze brakuje do szczęścia? Tablic, brakuje nam zmiennych mogących przyjmować wiele wartości tego samego typu. W NGinn jest to rozwiązane tak, że każdą zmienną możemy oznaczyć jako typ tablicowy – czyli mając zmienną int możemy powiedzieć że to tablica, wtedy przyjmuje ona wiele wartości typu int.
Ok, to chyba czas pokazać jak wygląda to od strony języka opisu procesu:
<variables> <variable name="UserName" type="string" dir="In" required="true" isArray="false" /> <variable name="BirthYear" type="int" dir="In" required="true" isArray="false" /> <variable name="PhoneNumbers" type="string" dir="In" required="true" isArray="true" /> <variable name="RegistrationId" type="string" dir="Out" /> </variables>
Sekcja ‘variables’ występuje w definicji procesu oraz w definicji zadania, w obu przypadkach ma dokładnie tę samą postać pokazaną wyżej. Mam nadzieję że nie ma potrzeby opisywania co znaczy ten xml – wszystko powinno być jasne po przeczynaniu tekstu powyżej.
No dobra, to wiemy jak zdefiniować zmienne w procesie oraz w zadaniu. W kolejnych postach dowiemy się jak zrobić, żeby zmienne z procesu trafiły do zadania, a po zakończeniu zadania aby wróciły do procesu i zaktualizowały jego dane. Będzie też artykuł o definiowaniu własnych typów danych. A potem jeszcze mnóstwo innych informacji.
NGinn 2.0 – kilka szczegółów
Od czasu ostatnich wieści na temat NGinn upłynęło trochę czasu. Wieści nie było głównie dlatego że nie miałem czasu zajmować się blogiem. Ale zajmowałem się NGinn, szczególnie koncepcją drugiej wersji języka i silnika BPM. Tak, powstaje druga wersja NGinn mimo tego że pierwsza nie miała żadnego wydania – uznałem że nie ma sensu kontynuować NGinn v1.0 z uwagi na potrzebę silnego przerabiania frameworku w celu doimplementowania nowych funkcji które w międzyczasie przyszły mi do głowy. Dlatego na bazie NGinn 1.0 jeszcze raz zdefiniowałem język i pracuję nad nową wersją silnika NGinn v 2.0. Dziś kilka informacji o tym co będzie w wersji 2.
Po pierwsze, nowy diagram, żeby było o czym dyskutować:

Różnica jest już w wyglądzie schematu, bo używam nowego narzędzia do diagramowania – jest to pakiet ‘aiSee’ firmy AbsInt. Generuje fajne grafy i mimo tego że pakiet zawiera trochę bugów i czasami jest narowisty, to i tak uważam że jest lepszy niż darmowy graphviz (w sumie aiSee też jest darmowy dla niekomercyjnych zastosowań). No, ale nowy generator obrazków to żaden przełom, więc idźmy dalej
Dość istotnym rozszerzeniem w stosunku do v1.0 jest dodanie podzadań złożonych (composite task), czyli takich podprocesów w wersji inline. Na obrazku jest to zadanie w przerywanej prostokątnej ramce, w środku zawiera taki mniejszy proces – i o to w sumie chodzi. Do czego jest nam potrzebne takie zadanie – otóż można bez niego żyć, ale co to za życie… Głównie chodzi o obsługę błędów – dla takiego zadania możemy globalnie obsłużyć błędy, podobnie jak w strukturalnych językach używając bloku try-catch. Zadanie złożone to taki wielki blok ‘try’, zaś doczepione do niego przejście z dwiema czerwonymi kreseczkami (double slash) reprezentuje blok ‘catch’ – czyli handler błędu. Działa to tak, że jeśli w trakcie realizacji podprocesu z zadania złożonego wystąpi jakiś błąd, to podproces ten zostanie zakończony i zostanie uruchomiona obsługa błędu.
Ważna rzecz- zadania złożone można dowolnie zagnieżdżać – czyli możemy mieć wielopoziomową obsługę błędów zupełnie jak w zagnieżdżonych blokach try-catch. Ale nie tylko do obsługi błędów przydają się te zadania. Możemy na przykład bardzo uprościć strukturę grafu jeśli opisujemy ‘przejścia anulujące’ – czyli połączenia które powodują usuwanie tokenów z procesu. Na rysunku jest to czerwona kropkowana strzałeczka z zadania T3 do miejsca po_T4. Mówi ona ‘po zakończeniu T3 usuń wszystkie tokeny z miejsca po_T4 i anuluj wszystkie zadania które wymagają tokenu w miejsciu po_T4′. Używając zadania blokowego możemy anulować całą podgrupę zadań jednym połączeniem anulującym zamiast prowadzić takie połączenie do każdego zadania z grupy indywidualnie.
Kolejna rzecz to obsługa kompensacji – podobnie jak w ‘profesjonalnych’ (tych droższych
komercyjnych systemach BPM-owych. Kompensacje to zadania wykonywane w celu anulowania skutków wykonania innych zadań. Będą zrealizowane podobnie jak obsługa błędów, ale to dopiero w NGInn v2.5
Jest jeszcze parę zastosowań zadań blokowych, ale o tym innym razem.
Co jeszcze? Możliwość kończenia procesu w wielu miejscach końcowych ( w NGinn 1.0 musiało być pojedyncze miejsce końcowe). Dochodzą też nowe zadania – ale o tym w kolejnych artykułach.
Jeśli chodzi o implementację silnika NGinn to w v 2.0 kompletnie zmienił się jego projekt. W 1.0 proces był pojedynczym ‘dokumentem’ scalającym w sobie stan wszystkich zadań, dane i historię. W v 2.0 postanowiłem maksymalnie ‘usamodzielnić’ zadania. Każde zadanie jest niezależnym bytem i może istnieć w oderwaniu od pozostałych. Oddzielnie przechowuje się jego stan – czyli stan procesu nie jest już jednym dokumentem a zestawem wielu dokumentów. Za koordynację takich samodzielnych zadań odpowiadają zadania blokowe – czyli te o których mowa była wyżej. Tak naprawdę to proces jest teraz takim dużym zadaniem blokowym, które może zawierać albo inne zadania blokowe, albo zadania atomowe (czyli te które faktycznie coś robią) – i tak dalej – dowolnie można to zagnieżdżać. Dlaczego taka decyzja – otóż autonomiczna obsługa zadań zwiększa nam stopień współbieżności w systemie, można jednocześnie aktualizować stan wielu zadań w tym samym procesie, łatwiej również zapanować nad rozmiarem danych opisujących zadania. Szybciej działa zapis i odczyt stanu pojedynczego zadania niż całego procesu – a zwykle interesuje nas dostęp do pojedynczego zadania i lepiej gdy nie musimy wczytywać w tym celu stanu całego procesu.