Rozdział 43 Warstwa bazy danych w aplikacjach trójwarstwowych opartych na technologiach PHP i XML Streszczenie. W rozdziale tym zostały opisane wybrane mechanizmy utworzone podczas realizacji dużego przedsięwzięcia informatycznego. Tworzony system został zaprojektowany jako aplikacja intranetowa. Działające moduły obsługiwane są za pośrednictwem przeglądarki WWW. Z programistycznego punktu widzenia jest to aplikacja trójwarstwowa. W dalszej części rozdziału opisane są podstawowe mechanizmy zaimplementowane w warstwie bazy danych tej aplikacji. 1 Wstęp Tworzenie dużych systemów informatycznych, których celem jest przetwarzanie danych dostarczanych przez operatora systemu, można podzielić na dwa ściśle ze sobą związane etapy. Pierwszy, polega na utworzeniu odpowiedniej struktury bazy danych, wraz z mechanizmami niezbędnymi do poprawnego jej działania, lub realizującymi zadania usprawniające pracę z systemem. Następnie tworzona jest aplikacja zapewniająca odpowiednią obsługę, zarówno danych wejściowych, jak i wyjściowych. W większości projektów informatycznych baza danych jest ściśle związana z aplikacją. W warunkach dużej integracji wspomnianych elementów, dobrym zwyczajem jest wyodrębnienie mechanizmów znajdujących się na ich pograniczu i utworzenie tak zwanej warstwy bazy danych w części aplikacyjnej systemu. Jednostkę taką można wyodrębnić w sytuacji, gdy nasza aplikacja jest aplikacją trójwarstwową. Rozdział ten prezentuje pewne specyficzne mechanizmy zastosowane w warstwie bazy danych, w rozbudowanej aplikacji intranetowej tworzonej na Uniwersytecie Łódzkim. 2 Warstwa bazy danych Opisywane poniżej mechanizmy są prezentacją praktycznego rozwiązania, które zostało zastosowane i nadal jest wykorzystywane przez kilkunastoosobową grupę programistów. Na bazie swoich doświadczeń, w tym i w poprzednich projektach, stworzyli oni zestaw mechanizmów nazwany RPG-silnikiem. Jego głównymi zadaniami są: Marcin Lizis: Uniwersytet Łódzki, Instytut Studiów Informatycznych, ul. Konstytucji 3 Maja 65/67, 97-200 Tomaszów Mazowiecki, Polska email: marcinl@math.uni.lodz.pl Sebastian Wojczyk: Uniwersytet Łódzki, Katedra Analizy Matematycznej i Teorii Sterowania, ul.banacha 22, 90-238 Łódź, Polska email: wojczyk@math.uni.lodz.pl
M. Lizis, S. Wojczyk zdefiniowanie sformalizowanego interfejsu programistycznego, dostarczenie zaawansowanych mechanizmów dla warstwy bazy danych, aplikacji i prezentacji, sterowanie przepływem danych między warstwami, sterowanie przepływem danych między systemem a operatorem aplikacji. Przed opisem konkretnych mechanizmów bazodanowych należy wspomnieć o podstawowych pojęciach związanych z RPG-silnikiem. Najistotniejszym pojęciem wymagającym komentarza jest definicja pola. Pole jest głównym nośnikiem informacji. Chodzi tu zarówno o wartości przechowywane w bazie danych, jak i o wszelkie parametry wykorzystywane w poszczególnych warstwach, np. sposób sprawdzania poprawności danych czy prezentacja danych użytkownikowi systemu. Z punktu widzenia użytkownika systemu pole można zidentyfikować jako odrębny element, umieszczony na stronie aplikacji. Takim elementem może być proste pole tekstowe, jak również pole złożone z kilku elementów określających, np. datę. W każdym komponencie programistycznym została zdefiniowana tablica, której zadaniem jest przechowywanie wszystkich pól wykorzystywanych w tym komponencie. Warto tu również wspomnieć, że pola są obiektami pewnej klasy. Dla każdego z tych obiektów istnieje możliwość zdefiniowania dużej liczby parametrów. 2.1 Odwzorowanie struktury bazy danych Coraz częściej podczas tworzenia systemów informatycznych rezygnuje się ze stosowania modelu kaskadowego, który jeszcze do niedawna uznawany był jako wzorcowy. Obecnie bardzo często stosuje się metodologię XP 1. Głównym powodem popularności tej metodologii jest maksymalne ograniczenie czasu potrzebnego na utworzenie prototypu aplikacji. Związane jest to bezpośrednio z faktem, że nie ma jasno rozgraniczonych ról związanych z analizą, projektem i implementacją systemu. W związku z tym, uczestnicy projektu posiadają ogólną wiedzę o całym systemie. Chodzi tu zarówno o część aplikacyjną, jak i bazodanową. W szczególności, znają oni strukturę bazy danych, wzajemne relacje między tabelkami oraz zależności między danymi umieszczonymi w tych tabelkach. W takiej sytuacji, z punktu widzenia programisty, naturalne wydaje się odwzorowanie struktury bazy danych w części aplikacyjnej. Jak już było wspomniane wcześniej, podstawowym nośnikiem danych jest pole. Wszystkie pola mają zdefiniowaną nazwę. Nazwa składa się z dwóch członów. Pierwszy określa nazwę tabelki, drugi jest odpowiednikiem nazwy kolumny. Dzięki takiej konwencji programista ma świadomość, na jakich danych pracuje. Bardzo istotne jest również ograniczenie ilości pomyłek, związanych z używaniem niewłaściwych zmiennych w zapytaniach SQL. Poza tym, programista może skorzystać ze standardowych wzorców pól przypisanych do poszczególnych kolumn w tabelach bazodanowych. W związku z powyższym jako przykłady nazw pól można podać: OSOBAnazwisko, ZATRUDNIENIEdataod, NIEOBECNOSCtyp. 1 XP - extreme Programming 366
Warstwa bazy danych w aplikacjach trójwarstwowych opartych na technologiach PHP i XML 2.2 Automatyczne generowanie pól Programista tworzący określony komponent aplikacji, na początku definiuje wszystkie pola, które będą użyte w dalszej części. Odwołania do określonej kolumny z określonej tabelki pojawiają się w wielu komponentach. W takiej sytuacji, w wielu miejscach trzeba by definiować podstawowe właściwości dla określonego pola. Nie miałoby to większego sensu, ponieważ wtedy mielibyśmy powtarzający się kod w wielu miejscach w aplikacji, który byłby identyczny i niesamowicie kłopotliwy w pielęgnacji. W związku z tym, postanowiono utworzyć schematy tworzenia i inicjowania poszczególnych pól aplikacji. W zależności od typu i specyfiki określonego pola można wydzielić następujące grupy czynności wykonywanych w tak zwanym generatorze pól: określenie typu pola parametr bezpośrednio związany z bazodanowym typem kolumny, np. tekst, data, liczba, itd.; zdefiniowanie parametrów bezpośrednio związanych z typem na przykład dla pól tekstowych określamy maksymalny rozmiar tekstu, który będzie mógł być umieszczony w odpowiedniej kolumnie; ustawienie wartości dostępnych dla niektórych pól istnieje możliwość określenia listy dostępnych wartości, która może być automatycznie zainicjowana w generatorze pól. Właściwość ta jest szczególnie przydatna dla wszelkich pól słownikowych. Załóżmy, że stworzymy komponent do obsługi adresów pracowników. Komponent może być na tyle elastyczny, że będzie obsługiwał dowolną liczbę adresów dla każdego pracownika, z takim ograniczeniem, że dla jednej osoby możemy mieć maksymalnie tyle adresów ile mamy określonych ich typów. W związku z tym, do każdego adresu będzie musiał być przyporządkowany parametr określający typ danego adresu. Pole obsługujące tą właściwość może mieć automatycznie ustawianą listę dostępnych wartości, na podstawie danych umieszczonych w odpowiednim słowniku; określenie typu walidacji, konieczności wypełnienia oraz poprawności wypełnienia danego pola po przesłaniu informacji od operatora systemu do odpowiedniego komponentu odbywa się walidacja danych. Jeżeli wypełnienie pola jest obowiązkowe, to użytkownik jest zobowiązany do umieszczenia w nim wartości. Następnie sprawdzana jest poprawność wprowadzonych wartości, np. w polu do przechowywania daty możemy wprowadzać tylko liczby i dodatkowo muszą one tworzyć poprawną datę (np. nie można wprowadzić miesiąca o numerze 20). Jeżeli przynajmniej jeden etap walidacji nie powiedzie się, wtedy automatycznie ustawiany jest parametr mówiący o niewłaściwym wypełnieniu pola. Jeżeli szablony poszczególnych pól są zdefiniowane, to w dowolnym komponencie istnieje możliwość utworzenia obiektu pola. W tym celu wystarczy wykonać jedną instrukcję: new GeneratorPol( OSOBAnazwisko ); Rozwiązanie takie niesie ze sobą wiele korzyści: uproszczona pielęgnacja kodu, uproszczona procedura tworzenia pól, ograniczenie ilości kodu dzięki zastosowaniu mechanizmów automatycznego tworzenia i przetwarzania pól, zunifikowane nazewnictwo pól. 367
M. Lizis, S. Wojczyk 2.3 Zarządzanie połączeniem bazodanowym Jak już było wspomniane na początku, aplikacja, w której zostały zastosowane opisywane mechanizmy, jest ściśle związana z bazą danych. Podczas projektowania systemu założono, że musi on być niezależny od Systemu Zarządzania Bazą Danych (SZBD). W związku z tym, należało stworzyć byt pośredniczący między konkretnymi zapytaniami a SZBD. Przy takim rozwiązaniu oczywiste jest, że struktura bazy danych musi być identyczna, bez względu na rodzaj SZBD. Po określeniu założeń stworzono mechanizm spełniający następujące role: nawiązywanie i zrywanie połączenia z bazą danych, wykonywanie konkretnych zapytań i zwracanie ewentualnych wyników w zunifikowanym formacie, zapewnienie wykonywania zapytań z uprawnieniami przydzielonymi dla danego operatora, zarządzanie transakcjami, monitorowanie czasu wykonania poszczególnych zapytań oraz całych komponentów, logowanie zapytań, które były błędne wraz z całym kontekstem (nazwa komponentu źródłowego, dane wejściowe, dane z sesji itp.). Wypunktowane mechanizmy zostały zaimplementowane dla dwóch baz danych (PostgreSQL, MySQL), w celu przetestowania rozwiązania i znalezienia ewentualnych problemów. Po udanych próbach można było stwierdzić, że mechanizm spełnia wszystkie stawiane mu założenia. Poza korzyściami, bezpośrednio związanymi z powyższymi punktami, należy jeszcze wspomnieć, że użycie mechanizmu znacznie uprościło pracę programistom, wyręczając ich z powtarzających się czynności. Przełączania z jednego SZBD na inny są zupełnie niewidoczne dla programistów i nie wymagają od nich ingerencji w kod aplikacji. Polegają one na zmianie jednego wpisu w pliku konfiguracyjnym. Z punktu widzenia programisty, cały czas posługuje się on takim samym obiektem. Należy jeszcze zauważyć, że opisane rozwiązanie wymaga użycia standardowych zapytań SQL, bez stosowania specyficznych rozwiązań zaimplementowanych w danym SZBD. 2.4 Interfejs pola w warstwie bazy danych Podczas tworzenia aplikacji trójwarstwowych musimy zaimplementować mechanizmy umożliwiające korzystanie z przepływających informacji w każdej warstwie systemu. Ponieważ w omawianym przypadku nośnikiem informacji są pola, muszą one być wyposażone w mechanizm prostego ustawiania wartości, pochodzących z bazy w konkretnej instancji pola oraz przekazywania tej wartości do konkretnego zapytania, w odpowiedniej formie. W tym celu zostały stworzone dwie metody realizujące powyższe zadania. Dzięki temu uzyskano pełną, oczekiwaną funkcjonalność i prostotę użycia. Jedynym zadaniem metody otrzymującej dane z bazy jest ich właściwe umieszczenie w obiekcie pola. Jej użycie ilustruje przykład: 368 $this->pola[ OSOBAdataurodzenia ]->ustawwartosc( 1980-01-01 ); Druga z opisywanych metod musi wykonać nieco więcej czynności. Jej zadaniem jest przekształcenie wewnętrznego formatu przechowywanych danych na wartość, która może być umieszczona w zapytaniu. Przy typach prostych będzie to polegało jedynie na zwróceniu odpowiedniej wartości. Przy typach złożonych, takich jak na przykład listy wielokrotnego wyboru, trzeba jeszcze wykonać inne czynności. Metoda ta może być stosowana analogicznie jak poprzednia:
Warstwa bazy danych w aplikacjach trójwarstwowych opartych na technologiach PHP i XML $this->pola[ OSOBAdataurodzenia ]->dajwartoscsql(); $this->pola[ S_STANOWISKOid ]->dajwartoscsql(); Pierwsza z tych metod może zwrócić 1980-01-01, natomiast druga, np. (\ 3\,\ 56\,\ 132\ ). Metodę tą można zastosować w następujący sposób: SELECT nazwisko,imie FROM osoba WHERE dataurodzenia=.$this->pola[ OSOBAdataurodzenia ]->dajwartosc(); 2.5 Mechanizmy sprawdzania uprawnień do danych W każdym dużym systemie informatycznym, z którego jednocześnie może korzystać wiele osób, powinny być zaimplementowane mechanizmy sprawdzania uprawnień do danych, które zamierza on przetwarzać. W tworzonej przez nas aplikacji, wymagania użytkownika definiowały rozbudowany system uprawnień. Jego główne założenia to: administrator może dowolnie przydzielać lub odbierać uprawnienia użytkownika do wszelkich komponentów systemu, jeżeli nawet użytkownik ma dostęp do pewnego komponentu to i tak może mieć ograniczoną możliwość przetwarzania danych. Możliwości ograniczenia dostępu do danych mogą być definiowane dowolnie, np. możliwość przetwarzania pracowników zatrudnionych w określonej jednostce organizacyjnej, przetwarzanie pracowników zajmujących określone stanowisko itp. Przy takich założeniach możliwe były dwie drogi ich realizacji: uprawnienia na poziomie aplikacji przy takim rozwiązaniu wszystkie tworzone komponenty musiałyby być wyposażone w skomplikowane mechanizmy kontroli uprawnień. W pewnych sytuacjach programista musiałby umieszczać bardzo podobny kod w wielu miejscach danego komponentu. Takie rozwiązanie znacznie zaciemniałoby kod aplikacji i negatywnie wpływało na łatwość jego pielęgnacji, uprawnienia na poziomie bazy danych w takiej sytuacji istnieje możliwość zaimplementowania praw dostępu w taki sposób, aby programista aplikacyjny mógł stworzyć swój komponent bez jakiegokolwiek dostosowywania kodu do ewentualnych uprawnień. Cały komponent może być zdefiniowany tak, jakby operator miał dostęp do całej bazy danych. Po szczegółowej analizie zagadnienia wybrano drugi wariant. W celu realizacji założeń postanowiono, że każdy komponent (programista tego komponentu) będzie miał zdefiniowane wymagane uprawnienia do odpowiednich tabel. Rozwiązano to tworząc specjalną tablicę, której zadaniem jest przetrzymywanie tabel niezbędnych do poprawnego działania. Dla każdej takiej tabeli zdefiniowano cztery podstawowe działania (SELECT, INSERT, UPDATE, DELETE), które komponent może wykonywać lub nie. Najistotniejsza jest jednak możliwość ograniczenia danej operacji (np. SELECT) do określonych wierszy. $uprawnienia[ osoba ][ select ]= nazwisko = Nowak ; $uprawnienia[ zatrudnienie ][ update ]= dataod> 2000-01-01 ; W pierwszym przykładzie widzimy ograniczenie polegające na wybieraniu z tabelki osoba jedynie wpisów dla osób mających nazwisko Nowak. W drugim przykładzie będziemy mogli wykonać uaktualnienie (UPDATE) danych rekordów, w których data zatrudnienia jest większa od 2000-01-01. Oprócz powyższych, prostych przypadków istnieje możliwość 369
M. Lizis, S. Wojczyk parametryzowania uprawnień wartościami, które są definiowane podczas nadawania uprawnień przez administratora systemu. W warstwie bazy danych istotne jest odpowiednie zdefiniowanie tablicy z danymi określającymi uprawnienia. Wszelkie czynności związane z realizacją określonych praw spoczywają na rozbudowanych mechanizmach działających po stronie bazy danych. 3 Podsumowanie W rozdziale opisano wybrane mechanizmy warstwy bazy danych z RPG-silnika, stanowiącego podstawę do budowy dużych aplikacji trójwarstwowych. Na podstawie obserwacji i analizy prac nad projektem można dostrzec wiele zalet stworzonego mechanizmu. Można do nich zaliczyć: prostotę wykorzystania korzystanie z opisanych mechanizmów jest bardzo proste i nie wymaga żadnych dodatkowych umiejętności w stosunku do metody tradycyjnej tworzenia podobnych aplikacji; uporządkowanie kodu wynika z narzuconego przez RPG-silnik standardu kodowania. Wiąże się to także z ułatwieniem późniejszego wyszukiwania błędów oraz dalszym utrzymaniem i ewentualnymi zmianami w działającej aplikacji; zwiększenie produktywności programistów spowodowane przejęciem obsługi pewnych powtarzających się czynności przez RPG-silnik. Opisane mechanizmy bazodanowe spełniają wszystkie wymagania założone podczas rozpoczynania prac nad projektem. Mimo to, podczas realizacji kolejnych części systemu, nadal pojawiają się propozycje, mające na celu zwiększenie wydajności mechanizmów oraz poprawę jakości kodu. Literatura 1. Astels D., Miller G., Novak M. Extreme Programming, Helion, 2002. 2. Stones R., Matthew N. Bazy danych i PostgreSQL. Od podstaw. Helion, 2002. 3. Williams H. E., Lane D. PHP i MySQL. Aplikacie bazodanowe, Helion, 2004. 4. http://www.extremeprogramming.org/ 5. http://www.postgresql.org/ 6. http://www.php.net/ 370