Archive for Październik, 2009
Jak wywołać dowolny web service bez WSDL i bez generowania kodu
W dzisiejszym artykule zajmiemy się wywoływaniem web services z poziomu procesu NGinn, przy okazji poznając alternatywny sposób korzystania z web services. Jak pewnie doskonale wszyscy wiedzą, web services są komponentami (uslugami) dostępnymi zdalnie, za pośrednictwem niezależnego od platformy protokołu SOAP. Wywołanie web service jest niczym innym jak przekazaniem komunikatu SOAP oraz odebraniem odpowiedzi (też SOAP), najczęściej za pomocą protokołu http.
Jednak gdy programista chce w swojej aplikacji wywołać jakiś web service zwykle nie posługuje się on bezpośrednio SOAPem, ale używa dostępnych w danym języku narzędzi ‘opakowujących’ które pozwalają mu wywoływać web service tak jakby to było wywołanie lokalnej funkcji . W Microsoft.Net posługujemy się na przykład narzędziem WSDL.exe które na podstawie XML-owego opisu usługi (WSDL) generuje nam kod w C# (lub VB) który opakowuje nam web service do postaci ‘strawnej’ dla .Net, czyli obiektu określonej klasy z interfejsem zgodnym z definicją web service. Jest to na tyle wygodne i uniwersalne że w typowych zastosowaniach możemy polegać tylko na wsdl.exe i w ogóle zapomnieć o XML-u i http pod spodem. Ale czasami wsdl.exe nie wystarcza – na przykład gdy nie wiemy z góry jaki web service będziemy wywoływać.
WSDL.exe generuje kod opakowujący web service, co oznacza że struktura web service jest wkompilowana w program który z danej usługi korzysta – i gdy usługa się zmieni to należy ponownie wygenerować kod wsdl.exe’m i skompilować aplikację od nowa. Świetnie, ale gdy aplikacja musi się komunikować z web service którego nie znamy na etapie kompilacji to WSDL.exe do niczego się nie przyda. NGinn jest właśnie takim przypadkiem ponieważ oferuje możliwość wywoływania web services w ramach procesów. Wszelkie potrzebne do wywołania informacje czerpie z definicji procesu a na podstawie tych informacji tworzy i wysyła komunikaty SOAP.
Żeby wywołać web service z procesu NGinn trzeba posłużyć się zadaniem ‘XmlHttp’. Jest to zadanie pozwalające na wykonywanie wywołań http, między innymi takich które zawierają XML. Żeby wywołać web service trzeba tylko zadbać o to aby ten XML był SOAP-em o strukturze której spodziewa się nasz web service – czyli zapewnić ‘binding’ między danymi na których operuje nasz proces a odpowiednimi polami w komunikacie SOAP. Na aktualnym etapie rozwoju NGinn robi się to za pomocą transformacji XSLT. Żeby było łatwiej to wszystko przyswoić posłużmy się przykładem. Jest taka publicznie dostępna usługa http://aspspider.info/trutech/MarketWatch.asmx która pozwala m.in . zapytać się o kursy akcji, indeksy giełdowe i kursy metali szlachetnych. Udostępnia ona operację ‘GetQuote’ za pomocą której będziemy odpytywać się o kursy akcji. Po wejściu na w/w stronę ukazuje się nam opis usługi (standardowo wygenerowany przez ASP.Net) z WSDL oraz z przykładami komunikatów SOAP. No i przykładowy komunikat SOAP dla zapytania o kurs akcji Microsoftu wygląda tak:
[xml]
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetQuote xmlns="http://trutech.com/marketwatch/">
<credValidate>
<Email>trutech.shared@gmail.com</Email>
<Password>trutech.shared@gmail.com</Password>
</credValidate>
<MyScript>
<sScriptCode>MSFT</sScriptCode>
</MyScript>
</GetQuote>
</soap12:Body>
</soap12:Envelope>
[/xml]
Gdy wyślemy taki XML POST-em na adres http://aspspider.info/trutech/MarketWatch.asmx dostaniemy odpowiedź która wygląda mniej-więcej tak:
[xml]
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetQuoteResponse xmlns="http://trutech.com/marketwatch/">
<GetQuoteResult>true</GetQuoteResult>
<MyScript>
<sScriptCode>MSFT</sScriptCode>
<sScriptName>MSFT – Microsoft Corpora</sScriptName>
<sScriptNameFromServer>Microsoft Corpora</sScriptNameFromServer>
<sScriptStockExchange>NasdaqNM</sScriptStockExchange>
<sScriptLastPrice>28.22 Rs</sScriptLastPrice>
<sScriptPreviousClose>28.22 Rs</sScriptPreviousClose>
<sScriptChangeInRupees>0.00 Rs</sScriptChangeInRupees>
<sScriptChangeInPercentage>0.00 %</sScriptChangeInPercentage>
<sScriptVolume>0 Sh</sScriptVolume>
<sScriptDaysRange>N/A – N/A</sScriptDaysRange>
<sScriptYearRange>14.87 Rs – 29.35 Rs</sScriptYearRange>
<sScriptToolTipText>
Registration Code : MSFT
Company Name : MSFT – Microsoft Corpora
Exchange : NasdaqNM ( 2009-10-29 )
Last Price : 28.22 Rs
Previous Close : 28.22 Rs
Change (INR) : 0.00 Rs
Change (%) : 0.00 %
Trading Volume : 0 Shares
Price Range (Day) : N/A – N/A
Price Range (52 Weeks) : 14.87 Rs – 29.35 Rs
Query Time : 2009-10-30 17:02:57
</sScriptToolTipText>
<sScriptWebLink>http://in.finance.yahoo.com/q?s=MSFT</sScriptWebLink>
<sScriptLastTradeDate>2009-10-29</sScriptLastTradeDate>
<sScriptLastTradeTime>4:00pm</sScriptLastTradeTime>
<sScriptQueryDateTime>2009-10-30 17:02:57</sScriptQueryDateTime>
<bScriptDataFound>true</bScriptDataFound>
<lScriptRowId>0</lScriptRowId>
</MyScript>
</GetQuoteResponse>
</soap:Body>
</soap:Envelope>
[/xml]
Interesująca nas cena jest w polu ‘sScriptLastPrice’. Niektórzy pewnie się zastanawiają co oznacza ‘Rs’ obok ceny – otóż jest to indyjski web service podający ceny w Rupiach. Bardzo przepraszam ale innego nie znalazłem.
Skoro już wiemy jak wyciągnąć cenę akcji Microsoftu w Rupiach to zbudujmy proces NGinn który dokładnie to robi – tj na wejściu bierze symbol spółki giełdowej i odpytuje się o cenę akcji tej spółki. Proces ten będzie miał jedną zmienną wejściową – symbol spółki, i jedną zmienną wyjściową – kurs akcji wyrażony w rupiach.
[xml]
<?xml version="1.0" encoding="utf-8"?>
<process version="1" name="RupeeStockQuote" xmlns="http://www.nginn.org/WorkflowDefinition.1_0.xsd">
<variables>
<variable name="symbol" type="string" required="true" dir="In" />
<variable name="price" type="string" required="true" dir="Out" />
</variables>
<body>
<places>
<place id="start" type="Start" />
<place id="end" type="End" />
</places>
<tasks>
<task id="T1" type="XmlHttp">
<variables>
<variable name="symbol" type="string" dir="In" required="true"/>
<variable name="price" type="string" required="true" dir="Out" />
</variables>
<parameters>
<param variable="Url"><value>http://aspspider.info/trutech/MarketWatch.asmx</value></param>
<param variable="RequestType"><value>SOAP</value></param>
<param variable="ResponseType"><value>SOAP</value></param>
<param variable="HttpMethod"><value>POST</value></param>
<param variable="RequestBodyXsl"><value>GetQuote_Request.xsl</value></param>
<param variable="ResponseBodyXsl"><value>GetQuote_Response.xsl</value></param>
<param variable="RequestHeaders"><expr>{"Content-Type": "application/soap+xml; charset=utf-8"}</expr></param>
</parameters>
</task>
</tasks>
<flows>
<flow from="start" to="T1" />
<flow from="T1" to="end" />
</flows>
</body>
</process>
[/xml]
Proces ten jest bardzo uproszczony – ma tylko jedno zadanie. W normalnej sytuacji miałby on dalszy ciąg, czyli logikę która w jakiś sposób działa w oparciu o cenę akcji i np. wysyła do kogoś powiadomienie. Ale to tylko przykład więc musi być prosty.
Widać tu że zadanie ‘T1’ posługuje się dwiema transformacjami XSL – pierwsza z nich jest zapisana w pliku ‘GetQuote_Request.xsl’ i służy do wygenerowania komunikatu SOAP wywołującego web service, druga, w pliku ‘GetQuote_Response.xsl’ służy do przetworzenia odpowiedzi web service na postać zrozumiałą dla NGinn. Oto te transformacje:
GetQuote_Request.xsl
[xml]
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:template match="/data">
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetQuote xmlns="http://trutech.com/marketwatch/">
<credValidate>
<Email>trutech.shared@gmail.com</Email>
<Password>trutech.shared@gmail.com</Password>
</credValidate>
<MyScript>
<sScriptCode><xsl:value-of select="symbol" /></sScriptCode>
</MyScript>
</GetQuote>
</soap12:Body>
</soap12:Envelope>
</xsl:template>
</xsl:stylesheet>
[/xml]
Na wejściu dostaje ona XML zawierający dane zadania ‘T1’, czyli coś w rodzaju
[xml]<data><symbol>MSFT</symbol></data>[/xml]
Na wyjściu natomiast powstaje komunikat SOAP o strukturze opisanej wcześniej – nasza transformacja po prostu wstawia zmienną ‘symbol’ w polu ‘sScriptCode’. Po wygenerowaniu komunikatu SOAP zadanie T1 wysyła go w treści requestu ‘POST’ pod adres web service. Następnie odbiera odpowiedź, czyli opisany wyżej komunikat ‘GetQuoteResponse’, który z kolei poddaje transformacji ‘GetQuote_Response.xsl’:
[xml]
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:quot="http://trutech.com/marketwatch/">
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:template match="/soap:Envelope/soap:Body/quot:GetQuoteResponse">
<data>
<price><xsl:value-of select="quot:MyScript/quot:sScriptLastPrice" /></price>
</data>
</xsl:template>
</xsl:stylesheet>
[/xml]
Widać że na wyjściu otrzymamy bardzo prosty XML w rodzaju
[xml]<data><price>28.08 Rs</price></data>[/xml]
NGinn na podstawie tego XML zaktualizuje zmienne zadania T1, czyli przypisze otrzymaną od web service wartość kursu akcji do zmiennej ‘price’. Na tym zadanie ‘T1’ się kończy, a razem z nim cały przykładowy proces.
Jest to bardzo elastyczna metoda wywoływania web services, mamy praktycznie całkowitą kontrolę nad strukturą przekazywanych komunikatów. Niestety, dzieje się to kosztem dość żmudnego przygotowywania XSL-i – nie ma żadnego narzędzia które by to zrobiło za nas (pewnie takie narzędzie korzystało by z WSDL, my nie musimy bo mamy przykłady XML-i). Warto tu wspomnieć o narzędziach które przydają się do tworzenia w/w xsl-i i w ogóle do testów web services. Pierwszym z tych narzędzi jest pakiet ‘Curl’ (http://curl.haxx.se/ ) umożliwiający wysyłanie po http dowolnych żądań (a w szczególności naszych komunikatów SOAP). Drugim jest pakiet ‘nxslt’ (http://www.xmllab.net/downloads/nxslt/) umożliwiający testowanie działania transformacji XSLT. Posługując się tymi dwoma narzędziami jesteśmy w stanie szybko ustalić jaka powinna być struktura komunikatów SOAP i wygenerować odpowiednie arkusze XSL. W ten sam sposób możemy obsłużyć nie tylko wywołania web services, ale np protokół XML-RPC czy inne, oparte na XML/HTTP protokoły komunikacji co czyni zadanie XmlHttp dość uniwersalnym narzędziem.
Taki sposób używania web services nie jest oczywiście zarezerwowany dla NGinn – można to robić w każdej innej sytuacji, nawet gdy mamy do dyspozycji Visual Studio z automatycznym generowaniem kodu klienta web service. Po co? Otóż są pewne typy danych których standardowa serializacja SOAP oferowana przez Microsoft.Net nie potrafi obsłużyć – na przykład typy nullable, nieprawidłowo też są obsługiwane pola typu ‘DateTime’ w innej niż UTC strefie czasowej. Innym przykładem są niestandardowe nagłówki http lub rozszerzenia SOAP – za pomocą opisanego tu sposobu możemy generować wywołania z dowolnymi nagłówkami i z dowolną treścią, podczas gdy w ‘normalny’ sposób musieli byśmy pisać w C#/VB dodatkowy kod obsługujący przypadki niestandardowe. No a poza tym jest to świetny sposób testowania web services bez pisania kodu.
Ponieważ opisany tu mechanizm wywoływania web services jest bardzo prosty, można wręcz powiedzieć że prymitywny, rodzi się pytanie czy jest to rozwiązanie docelowe czy tylko prowizoryczne? Na pewno brakuje mu kilku rzeczy – między innymi automatycznej kontroli zgodności schematów danych z definicją web service, można by się też pokusić o jakiś graficzny edytor pozwalający mapować zmienne zadania na parametry we/wy usługi lub też automatycznie generować XSL-e na podstawie jakiegoś prostszego mapowania. Ale mimo to uważam że jest to elastyczne i proste w użyciu narzędzie które da się zastosować w wielu sytuacjach.
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.