next up previous contents
Next: 6 Testy Up: Język do tworzenia aplikacji Previous: 4 Implementacja programu   Spis rzeczy

Subsections

5 Porównanie

1 Wprowadzenie

W tym rozdziale chciałbym przedstawić przykładową aplikację działającą za pośrednictwem protokołu HTTP oraz porównać program napisany w języku TLCC z analogicznymi programami zbudowanymi przy pomocy innych systemów. Istnieje wiele różnych narzędzi umożliwiających tworzenie serwisów WWW. Do najpopularniejszych należą języki skryptowe, takie jak PHP [12] czy ASP [2], a także pakiet J2EE [8] oparty na języku Java. W tym rozdziale zestawię fragmenty kodu w TLCC z odpowiadającymi im fragmentami programów w PHP i Javie.

Przykładowy serwis, którego dotyczą te porównania, to katalog stron WWW, pogrupowanych ze względu na kategorie. Poszczególne kategorie tworzą drzewiastą strukturę, której liśćmi są odnośniki do konkretnych stron. Użytkownicy posiadający odpowiednie uprawnienia mogą modyfikować poszczególne węzły struktury. Ponadto w przykładowym serwisie dostępne są też inne funkcje, takie jak: dodawanie zakładek do stron przez użytkowników czy grupy dyskusyjne, związane z poszczególnymi kategoriami. Rysunek 5.1 przedstawia diagram związków encji dla tej aplikacji.

Rysunek 5.1: Model danych aplikacji
 

W dalszej części rozdziału dokładnie opisuję reprezentację drzewa kategorii i implementację funkcji, które są z nią związane. Pełny kod źródłowy przykładu znajduje się na załączonej dyskietce.

Podwęzły związane z daną kategorią mogą być odnośnikami do konkretnych stron WWW (umieszczanych w tym katalogu) lub podkategoriami (które zawężają temat kategorii nadrzędnej). W danej grupie znajdują się tylko elementy tego samego typu (np. tylko podkategorie). Każdy węzeł posiada następujące atrybuty:

W zależności od wykorzystywanych narzędzi należy znaleźć taką reprezentację modelu danych, która będzie umożliwiała szybki dostęp do wszystkich elementów znajdujących się w kategorii oraz do wszystkich kategorii nadrzędnych (ponieważ należy wyświetlić użytkownikowi bieżącą ścieżkę oraz umożliwić powrót do dowolnego elementu na tej ścieżce).

2 Reprezentacja danych


1 Grupy tematyczne jako obiekty TLCC

Najwygodniej jest reprezentować poszczególne kategorie jako obiekty. Podobnie jak w systemie plików można przyjąć, że dowolny węzeł zawiera wszystkie informacje o jego węzłach potomnych (czyli tytuł, opis itp.). Zatem definicje odpowiednich struktur i klas, skrócone do samych atrybutów, będą wyglądały następująco:

// TLCC
struct NodeData
{
   string title;
   string description;
   string href;
   TopicNode topicNode;
} 

class TopicNode
{
   NodeData[] pathData;
   NodeData[] childrenData;
} 

Struktura NodeData zawiera dane pojedynczego węzła w drzewie. Natomiast klasa TopicNode reprezentuje grupę tematyczną. Grupa ta może zawierać zarówno podgrupy, jak i odnośniki do konkretnych stron.

Oprócz tego w klasie TopicNode należy zdefiniować różne metody pozwalające wykonywać operacje na węzłach, czyli dodawanie i usuwanie węzłów potomnych, pobieranie tytułu, opisu itp. Ponieważ każda grupa zawiera dane kategorii na ścieżce prowadzącej od korzenia do bieżącego węzła (wystarczy pamiętać tytuł i referencję do obiektu), więc nie ma potrzeby pobierania tych informacji ze wszystkich obiektów na tej ścieżce. Aby zapewnić, że tytuły kategorii nadrzędnych są aktualne, wystarczy co pewien czas sprawdzać, czy zgadzają się z danymi węzła, który jest ojcem (wszystkie zmiany będą przenoszone wraz z poruszaniem się w głąb drzewa).

W przypadku gdy grup tematycznych jest bardzo dużo trzeba się liczyć z tym, że każdorazowe odwołanie do pewnej grupy jest związane z odczytaniem obiektu z dysku. Lecz w opisywanym modelu przy wyświetlaniu danej grupy będziemy mieli jedynie odwołania do dwóch obiektów (bieżącej kategorii oraz jej ojca), więc obsługa żądań od użytkowników będzie szybka. Oczywiście za samo składowanie i aktywowanie obiektów odpowiada kod generowany automatycznie przez translator.

2 Grupy tematyczne przechowywane w bazie danych

Jeśli chcielibyśmy napisać naszą aplikację w PHP lub w Javie i nie napracować się nad przechowywaniem informacji o kategoriach, to najlepiej jest skorzystać z bazy danych (zasady projektowania baz danych opisane są w pozycji [3]). Potrzebujemy tabeli, w której będą składowane informacje o węzłach w drzewie grup. Jej strukturę zilustrowano w tabeli 5.1.


Tabela 5.1: Struktura tabeli NODE_DATA
nodeId int primary key
parentId int references NODE_DATA(nodeId)
title varchar(50)
description varchar(2000)
href varchar(500)


Jednak w tym przypadku pojawia się pewien problem. Aby pobrać tytuły kategorii na ścieżce od danego węzła do korzenia, trzeba dla każdego węzła na tej ścieżce wykonać osobne zapytanie. Jeśli korzystamy z bazy danych Oracle, to rozwiązaniem jest konstrukcja CONNECT BY PRIOR, ale w przypadku darmowych baz danych (np. MySQL czy PostgreSQL) trzeba wprowadzić dodatkową tabelę grupującą węzły nadrzędne dla poszczególnych kategorii. Jej strukturę ilustruje tabela 5.2.


Tabela 5.2: Struktura tabeli PARENTS_DATA
nodeId int references NODE_DATA(nodeId)
parentId int references NODE_DATA(nodeId)
parentIndex int


Widoczna jest także jeszcze jedna różnica między podejściem bazodanowym i obiektowym. W obiektach TLCC atrybuty wszystkich węzłów podrzędnych są zebrane w jednej tablicy, dzięki czemu mamy pewność, że ich odczyt z dysku będzie szybki. Natomiast w przypadku bazy danych informacje o każdej kategorii podrzędnej znajdują się w osobnym wierszu, a poszczególne wiersze wcale nie muszą być zapisane blisko siebie, więc czas ich odczytu będzie dłuższy. Innym rozwiązaniem może być umieszczenie w tabeli opisującej węzeł wszystkich informacji o potomkach (tak jak w obiektach). Dane te mogą być zapisane jako serializowane obiekty (Javy lub PHP) lub złożone typy danych, udostępniane przez PostgreSQL. Jednak w przypadku zmian wielkości tych danych (zakładamy, że do kategorii mogą być często dodawane nowe elementy) dostęp do nich będzie coraz wolniejszy, ze względu na fragmentację poszczególnych wierszy tabeli w bazie danych. Jeszcze inne podejście mogłoby polegać na stworzeniu specjalnego pliku do zapisu tych informacji. Lecz implementacja operacji na takim pliku byłaby znacznie bardziej czasochłonna niż stworzenie tabel lub definicji odpowiednich klas w TLCC.

Oprócz skryptów tworzących bazę danych lub kodu zarządzającego odpowiednim plikiem musimy jeszcze dodać do naszej aplikacji odpowiednie klasy (Javy lub PHP), które będą wykonywały zapytania i udostępniały informacje o poszczególnych kategoriach. Ponadto muszą istnieć funkcje, które w przypadku modyfikacji tych danych zmienią również zawartość bazy. Widać zatem, że musimy stworzyć kod, który zarządza nie tylko danymi lokalnymi aplikacji, ale również informacjami zgromadzonymi w bazie. W przypadku aplikacji napisanej w TLCC możemy się ograniczyć tylko do operacji na danych lokalnych, które są automatycznie zapamiętywane w sposób trwały.

Zatem w kolejnych przykładach zakładam, że w kodzie programów korzystających z bazy danych znajduje się klasa NodeData, która reprezentuje informacje o grupach i odnośnikach w naszej aplikacji. Jej definicja odpowiada definicji struktury NodeData, z p. 5.2.1. Wystarczy ją tylko przepisać na PHP lub Javę (referencji do węzła odpowiada jego identyfikator o nazwie nodeId).

3 Wykorzystanie systemu CORBA w Javie

Gdybyśmy chcieli zbudować w Javie aplikację o zbliżonej architekturze do programów generowanych przez translator TLCC, to wiązałoby się to z dodatkowym nakładem pracy. Trzeba by napisać takie programy jak: serwer główny, serwer obiektów (sesji) i serwer obiektów dzielonych. Natomiast w serwerze obiektów należałoby zaimplementować takie mechanizmy jak rejestracja obiektów, zapamiętywanie danych związanych z sesjami, rozpoznawanie wywołanych akcji, wizualizacje itp.

Ponadto potrzebna jest specjalna biblioteka, która korzystając z architektury POA zapewni trwałość obiektów i sesji użytkowników. Należałoby przy tym zmodyfikować mechanizm serializacji obiektów w taki sposób, aby łańcuchowe referencje używane w systemie CORBA były w pewien sposób kompresowane (w obrębie tego samego procesu olbrzymia większość danych zawartych w referencji jest identyczna dla wszystkich obiektów). Natomiast w TLCC opisywane problemy rozwiązywane są przez translator.

Nawet w przypadku napisania specjalnej biblioteki w Javie jej użycie nie byłoby wygodne. Przykładem mogą być atrybuty statyczne i dzielone, dla których trzeba tworzyć osobne klasy i ręcznie nimi zarządzać (należy np. zadbać aby widoczność atrybutów statycznych ograniczała się do bieżącej sesji). Innym przykładem jest konieczność blokowania obiektu, który obsługuje jakieś żądanie (aby nie została przeprowadzona jego dezaktywacja). Nie bez znaczenia są także różne utrudnienia, związane z bezpośrednim użyciem technologii CORBA (np. oddzielenie właściwego obiektu od zdalnego pośrednika, konieczność definiowania interfejsów lub potrzeba korzystania z różnych klas pomocniczych itp.).

3 Operacje na danych dzielonych


1 Dane dzielone w TLCC

Implementując fragmenty aplikacji obsługującej listy tematów można pokazać, że obsługa danych wspólnych dla użytkowników jest łatwiejsza w języku TLCC. Jako ilustrację można podać kod metod, które powinny się znaleźć w klasie TopicNode. Jednym z przykładów jest funkcja, która modyfikuje dane węzła potomnego, takie jak tytuł, opis i odnośnik.

// TLCC
void modifyChild(int index, NodeData nodeData)
{
   childrenData[i] = nodeData;
} 

Natomiast gdy chcemy pobrać dane wszystkich elementów podrzędnych, to używamy funkcji getChildrenData(), której implementacja jest również oczywista:

// TLCC
NodeData[] getChildrenData()
{
   return childrenData;
} 

Zatem korzystając z tego, że wszystkie informacje zapamiętujemy w zwykłej tablicy, możemy ją po prostu przekazać, bez jakiejkolwiek obróbki. Nie jest to możliwe w przypadku zastosowania bazy danych, co pokazują kolejne przykłady.

Wszelkie operacje na tablicach, wykonywane w przykładowym serwisie są proste i krótkie w implementacji. W dodatku B zostały opisane różne pomocnicze funkcje, które jeszcze bardziej ułatwiałyby korzystanie z tablic.

2 Dane dzielone w PHP

Implementując obsługę list tematów w PHP musimy w jakiś sposób przechowywać zgromadzone informacje. Załóżmy, że chcemy napisać w PHP klasę pomocniczą Database, której zadaniem jest wykonywanie operacji na bazie danych (np. MySQL). W tym punkcie znajdują się przykłady implementacji metod z tej klasy, których działanie jest analogiczne do tych przedstawionych w p. 5.3.1 (dla TLCC).

Oto funkcja modyfikująca węzeł o określonym identyfikatorze.

// PHP
function modifyNode($nodeData)
{
   $query = "UPDATE NODE_DATA SET TITLE='{$nodeData -> title}', ";
   $query .= "DESCRIPTION='{$nodeData -> description}', ";
   $query .= "HREF='{$nodeData -> href}' WHERE NODEID={$nodeData -> nodeId}";
   mysql_query($query);
} 

Natomiast gdy chcielibyśmy pobrać dane wszystkich węzłów podrzędnych aby wyświetlić zawartość grupy o konkretnym identyfikatorze, musimy zbudować tablicę na podstawie informacji zapisanych w bazie.

// PHP
function getChildrenData($nodeId)
{
   $query = "SELECT * FROM NODE_DATA WHERE PARENTID=$nodeId";
   $res = mysql_query($query);
   $nr = 0;
   while ($row = mysql_fetch_row($res))
   {
      $data = new NodeData();
      $data -> nodeId     = $row["nodeId"];
      $data -> title      = $row["title"];
      $data -> description = $row["description"];
      $data -> href       = $row["href"];
      $childrenData[$nr]  = $data;
      $nr++;
   }
   return $childrenData;
} 

Na podanym przykładzie wyraźnie widać, że to co uzyskaliśmy w TLCC przy pomocy jednego wiersza, w PHP wymaga dłuższego kodu. Dodatkowo TLCC ma tą przewagę, że dostęp do danych kategorii podrzędnych jest bardzo szybki, ponieważ zapisane są one blisko siebie (w tym samym obiekcie). Natomiast jeśli chodzi o bazę danych, to nie mamy żadnych gwarancji, że poszczególne wiersze tej tabeli nie będą oddalone od siebie na dysku (przypuszczenie to potwierdziło się w testach, opisanych w rozdziale 6).

3 Dane dzielone w Javie

Jeśli chodzi o Javę, to podobnie jak w PHP do przechowywanie informacji dzielonych wykorzystujemy bazę danych. Przypuśćmy, że tak jak w PHP implementujemy pomocniczą klasę Database, która wykonuje pomocnicze operacje. Oto kod funkcji modifyNode() i getChildrenData().

// Java
void modifyNode(NodeData nodeData)
{
   String query = "UPDATE NODE_DATA SET TITLE=?, ";
   query += "DESCRIPTION=?, HREF=? WHERE NODEID=?";
   PreparedStatement ps = conn.prepareStatement(query);
   ps.setString(1, nodeData.title);
   ps.setString(2, nodeData.description);
   ps.setString(3, nodeData.href);
   ps.setInt(4, nodeData.nodeId);
   ps.executeUpdate();
} 

Vector getChildrenData(int nodeId)
{
   String query = "SELECT * FROM NODE_DATA WHERE nodeId=?";
   PreparedStatement ps = conn.prepareStatement(query);
   ps.setInt(1, nodeId); 

   ResultSet rs = ps.executeQuery();
   Vector childrenData = new Vector();
   while (rs.next())
   {
      NodeData data   = new NodeData();
      data.nodeId     = rs.getInt("nodeId");
      data.title      = rs.getString("title");
      data.description = rs.getString("description");
      data.href       = rs.getString("href");
      childrenData.add(data);
   }
   return childrenData;
} 

W podanym kodzie zakładamy, że zmienna conn zawiera obiekt, który zapewnia połączenie z bazą danych. Można zauważyć, że definicje funkcji w Javie są trochę dłuższe od swoich odpowiedników w PHP (zwłaszcza funkcja modifyNode()), ale też trochę bardziej przejrzyste (ze względu na specjalne obiekty pośredniczące w wykonywaniu zapytań do bazy). Kod w Javie jest jednak znacznie dłuższy niż w TLCC (głównie jeśli chodzi o drugą funkcję), gdyż wymaga wykonywania zapytań w języku SQL.

4 Obsługa akcji


1 Akcje w TLCC

W opisywanym systemie zarządzania grupami tematycznymi użytkownik ma możliwość wykonania wielu różnych działań. Przykładowo, dla każdej wyświetlanej podgrupy użytkownik z odpowiednimi uprawnieniami może: wejść do tej podgrupy, rozpocząć jej modyfikację lub skasować podgrupę. Dla uproszczenia można pominąć przypadek, gdy wyświetlany element nie jest grupą, tylko końcowym odnośnikiem, a także sprawdzanie uprawnień użytkownika. Kolejny przykład zawiera kod obsługujący poszczególne akcje, który można umieścić w wizualizatorze.

// TLCC
NodeData[] data = currentTopicNode.getChildrenData();
[<table>]
for(int i = 0; i < data; i++)
{
   NodeData elem = data[i];
   [
      <tr>
      #{
         if (modifiedIndex == i);
            // tutaj należy wyświetlić formularz do edycji elementu
         else
         [
            <td><a href=#action{
               currentTopicNode = elem.topicNode;
               modifiedIndex = -1;
            }#><b>#elem.title#</b></a></td>
            <td><i>#elem.description#</i></td>
            <td><a href=#action{
               modifiedIndex=i}#>MODYFIKUJ</a></td>
            <td><a href=#action{
               currentTopicNode.removeChild(i)}#>USUŃ</a></td>
         ]
      }#
      </tr>
   ]
}
[</table>] 
 
Zakładam, że atrybuty currentTopicNode i modifiedIndex zawierają odpowiednio referencję do obiektu bieżącej kategorii i numer edytowanego elementu w tej grupie. Zakładam również istnienie metody TopicNode::removeChild(), która usuwa element potomny o podanym indeksie.


2 Akcje w PHP

Aby rozpoznawać akcje w PHP należy najpierw ustalić zmienną, przez którą będą one przekazywane (np. zmienna action). Następnie trzeba wprowadzić identyfikatory, poprzez które będziemy rozpoznawać akcję do wykonania. Najlepiej użyć w tym celu specjalnych zmiennych, aby nie podawać jako identyfikatorów konkretnych napisów.

// PHP
$ACTION_SET_TOPIC = "setTopic";
$ACTION_MODIFY  = "modify";
$ACTION_REMOVE   = "remove"; 

Przed wyświetleniem wynikowej strony WWW trzeba obsłużyć zdefiniowane akcje, żeby zawartość tej strony uwzględniała działanie użytkownika.

// PHP
switch ($action)
{
   case $ACTION_SET_TOPIC:
      $currentTopicId = $nodeId;
      $modifiedIndex = -1;
      break;
   case $ACTION_MODIFY:
      $modifiedIndex = $indexNum;
      break;
   case $ACTION_REMOVE:
      $database -> removeNode($nodeId);
}

Zakładamy, że mamy dostępne zmienne $currentTopicId oraz $modifiedIndex, które są umieszczone w sesji PHP. Ponadto na zmiennej $database znajduje się obiekt klasy Database, który odpowiada za komunikację z bazą danych.

Poniżej znajduje się już odpowiednik kodu z p. 5.4.1, przepisany na PHP.

// PHP
$data = $database -> getChildrenData();
$size = sizeof($data);
echo "<table>\n";
for($i = 0; $i < $size; $i++)
{
   $elem = $data[$i];
   echo "<tr>\n";
   if ($modifiedIndex == $i);
      // wyświetlenie formularza do edycji elementu
   else
      echo <<<EOT
         <td><a href="$REQUEST_URI?action=$ACTION_SET_TOPIC&
            nodeId={$elem -> nodeId}">
            <b>{$elem -> title}</b></a></td>
         <td><i>{$elem -> description}</i></td>
         <td><a href="$REQUEST_URI?action=$ACTION_MODIFY&
            indexNum=$i">MODYFIKUJ</a></td>
         <td><a href="$REQUEST_URI?action=$ACTION_REMOVE&
            nodeId={$elem -> nodeId}">USUŃ</a></td>
EOT;
   echo "</tr>\n";
}
echo "</table>\n"; 

Na podanym przykładzie widać, że parametry muszą być przekazywane jako łańcuchy, a przy obsłudze akcji nazwy tych parametrów stają się zmiennymi. W przypadku większej liczby akcji łatwo się pomylić i podać nieprawidłową nazwę parametru. Wszystkie odnośniki reprezentujące akcje musimy przygotować ręcznie. Ponadto wstawianie bloków kodu HTML nie jest tak czytelne jak w TLCC.

3 Akcje w Javie

Budowa kodu obsługującego akcje w serwletach Javy jest zbliżona do PHP. Jednak w Javie nie możemy łatwo wstawiać napisów składających się z wielu wierszy. Przechowywanie danych w sesji też wymaga trochę więcej pracy. Ponieważ dostęp do parametrów zapytania odbywa się za pośrednictwem zwykłych napisów, więc można zdefiniować stałe dla wszystkich obsługiwanych parametrów, akcji i zmiennych sesyjnych. Poprawność użycia tych parametrów zostanie sprawdzona podczas kompilacji.

// Java
static final String PARAM_ACTION    = "action";
static final String PARAM_NODE_ID   = "nodeId";
static final String PARAM_INDEX_NUM = "indexNum"; 

static final String ACTION_SET_TOPIC = "setTopic";
static final String ACTION_MODIFY   = "modify";
static final String ACTION_REMOVE   = "remove"; 

static final String CURRENT_TOPIC_ID = "currentTopicId";
static final String MODIFIED_INDEX  = "modifiedIndex"; 

Natomiast następny fragment kodu wykonuje działania odpowiadające określonym akcjom. Zakładam, że na zmiennej request znajduje się obiekt reprezentujący żądanie użytkownika (klasy HttpServletRequest), a na zmiennej session obiekt sesji związany z tym żądaniem (klasy HttpSession).

// Java
String action = request.getParameter(PARAM_ACTION);
if (action != null)
   if (action.equals(ACTION_SET_TOPIC))
   {
      session.putValue(CURRENT_TOPIC_ID, new Integer(
         Integer.parseInt(request.getParameter(NODE_ID))));
      session.putValue(MODIFIED_INDEX, new Integer(-1));
   }
   else
   if (action.equals(ACTION_MODIFY))
      session.putValue(MODIFIED_INDEX, new Integer(
         Integer.parseInt(request.getParameter(INDEX_NUM))));
   else
   if (action.equals(ACTION_REMOVE))
      database.removeTopicNode(Integer.parseInt(
         request.getParameter(NODE_ID))); 

Kolejnym krokiem jest wypisanie odpowiedniej strony WWW, która będzie zawierała listę tematów w grupie. Dodatkowo zakładam, że na zmiennej out typu PrintWriter znajduje się obiekt, do którego należy przekazać napisy mające się znaleźć w wygenerowanej odpowiedzi.

// Java
int currentTopicId =
   ((Integer) session.getValue(CURRENT_TOPIC_ID)).intValue();
int modifiedIndex =
   ((Integer) session.getValue(MODIFIED_INDEX)).intValue();
String requestURI = request.getRequestURI();

NodeData[] data = database.getChildrenData(currentTopicId);
out.println("<table>");
for(int i = 0; i < data.length; i++)
{
   NodeData elem = data[i];
   out.println("<tr>");
   if (modifiedIndex == i);
      // wypisanie formularza
   else
   {
      out.println("<td><a href=\"" + requestURI + "?" +
         PARAM_ACTION + "=" + ACTION_SET_TOPIC + "&" +
         PARAM_NODE_ID + "=" + elem.nodeId + "\">" +
         "<b>" + elem.title + "</b></a></td>");
      out.println("<td><i>elem.description</i></td>");
      out.println("<td><a href=\"" + requestURI + "?" +
         PARAM_ACTION + "=" + ACTION_MODIFY + "&" +
         PARAM_INDEX_NUM + "=" + i + "\">MODYFIKUJ</a></td>");
      out.println("<td><a href=\"" + requestURI + "?" +
         PARAM_ACTION + "=" + ACTION_REMOVE + "&" +
         PARAM_NODE_ID + "=" + elem.nodeId + "\">USUŃ</a></td>");
   }
   out.println("</tr>");
}
out.println("</table>"); 
 
W tym kodzie (podobnie jak w PHP) musimy ręcznie zakodować wszystkie identyfikatory i parametry akcji. Również generowanie odnośnika z akcją i sama obsługa akcji znajdują się w różnych miejscach programu. Samo tworzenie odnośników jest dosyć kłopotliwe. Można je trochę ułatwić pisząc funkcje pomocnicze, które pobierają pewne identyfikatory i na ich podstawie konstruują pełny URL. Jednak w takim przypadku z góry nie wiemy ile argumentów powinna pobrać ta funkcja, ponieważ liczba parametrów akcji może być dowolna (zatem trzeba będzie zbudować odpowiedni wektor). Nadal też będziemy musieli jawnie podać te wszystkie parametry, w przeciwieństwie do TLCC, gdzie są one wykrywane i przekazywane automatycznie.

5 Obsługa formularzy

1 Formularze w TLCC

Przy pomocy wbudowanych konstrukcji przeznaczonych do obsługi formularzy, można w języku TLCC bardzo łatwo zintegrować je z kodem programu. Dzięki temu elementy formularzy nie są tylko napisami generowanymi przez program, ale mają określoną semantykę. Oto brakujący kod z poprzedniego przykładu, w którym obsługuje się modyfikację pojedynczego elementu kategorii (którym jest podgrupa).

// TLCC
if (modifiedIndex == i)
[
   <form action=#form_action()# method="get">
      <td><input type="text" name=#field(elem.title)#
         value=#elem.title#></td>
      <td><input type="text" name=#field(elem.description)#
         value=#elem.description#></td>
      <td><input type="submit" value="OK"></td>
      #form_action{
         currentTopicNode.modifyChild(modifiedIndex, elem);
         modifiedIndex = -1;
      }#
   </form>
] 

Wykorzystujemy tutaj funkcję TopicNode::modifyChild(), która modyfikuje informacje o podanym węźle potomnym.

2 Formularze w PHP

Aby obsłużyć formularz trzeba zdefiniować odpowiednią stałą dla akcji z tego formularza. W kodzie obsługującym tą akcję będziemy mieć dostęp do wartości pól podanych przez użytkownika. Dodatkową trudnością jest konieczność przekazywania parametrów akcji w polach ukrytych tego formularza. Przykładowy kod stanowi brakującą część poprzedniego przykładu dla PHP (przy czym obsługę akcji należy umieścić na początku programu).

// PHP
$ACTION_MODIFY_TOPIC = "modifyTopic"; 

if ($action == $ACTION_SET_DATA)
{
   $nodeData = new NodeData($nodeId, $title, $description, "");
   $database -> modifyNode($nodeData);
   $modifiedIndex = -1;
} 

// ------------------------------------ 

if ($modifiedIndex == $i)
   echo <<<EOT
      <form action="$REQUEST_URI" method="get">
         <td><input type="text" name="title"
            value="{$elem -> title}"></td>
         <td><input type="text" name="description"
            value="{$elem -> description}"></td>
         <td><input type="submit" value="OK"></td> 

         <input type="hidden" name="action" value="$ACTION_MODIFY_TOPIC">
         <input type="hidden" name="nodeId" value="{$elem -> nodeId}">
      </form>
EOT; 

Cechą odróżniającą ten kod od przykładu w TLCC jest to, że formularz i obsługa akcji z nim związanej są rozdzielone oraz fakt, że programista musi przekazać wszystkie parametry akcji w polach ukrytych. Ponadto nie ma żadnego sprawdzania poprawności podanych stałych czy zmiennych, ale to już jest cechą samego PHP, który jest językiem skryptowym i nie wymaga podawanie typów zmiennych oraz kompilacji kodu przed uruchomieniem.

3 Formularze w Javie (fragment)

Podobnie jak w PHP czynności związane z przetwarzaniem formularzy muszą być w Javie zakodowane ręcznie. Analogicznie należy zdefiniować odpowiednie stałe i obsłużyć akcję dla formularza.

// Java
static final String ACTION_MODIFY_TOPIC = "modifyTopic"; 

static final String PARAM_NODE_ID    = "nodeId";
static final String PARAM_TITLE      = "title";
static final String PARAM_DESCRIPTION = "description";

if (request.getParameter(PARAM_ACTION).equals(ACTION_MODIFY_TOPIC))
{
   database.setTopicData(
      Integer.parseInt(request.getParameter(PARAM_NODE_ID)),
      request.getParameter(PARAM_TITLE),
      request.getParameter(PARAM_DESCRIPTION));
   session.putValue(MODIFIED_INDEX, new Integer(-1));
} 

Natomiast jeśli chodzi o samo wypisanie formularza, to podobnie jak w PHP wszystkie parametry akcji należy przekazać przy pomocy pól ukrytych. Tak samo jak w poprzednim przykładzie dla Javy (obsługa akcji) kolejne wiersze kodu HTML muszą być wypisywane lub dołączane do pewnego napisu. Samo rozwiązanie jest identyczne jak w przypadku PHP, a zmieniają się jedynie szczegóły składniowe. Dlatego druga część przykładu dla Javy została pominięta.

6 Obsługa sesji

1 Sesje w TLCC

Przypuśćmy, że chcemy rozszerzyć naszą aplikację o możliwość zapamiętywania ulubionych kategorii użytkownika (np. tych, które może edytować lub często przegląda). Lista tych tematów byłaby wyświetlana w formie zakładek, które umożliwiałyby natychmiastowe przejście do określonej kategorii. Ponieważ zestaw tych tematów byłby unikatowy dla każdego użytkownika, więc można po prostu zapamiętać je jako listę obiektów, z których każdy reprezentuje pojedynczą zakładkę. Atrybutami takiego obiektu byłyby np. tytuł, pełna ścieżka, komentarz użytkownika, odnośnik do obiektu kategorii itp. Tytuły i ścieżki mogłyby być weryfikowane w momencie skorzystania z zakładki przez użytkownika lub automatycznie co pewien czas (np. co kilka żądań byłaby weryfikowana jedna zakładka).

Poza metodami i wizualizatorami w samej klasie tego obiektu nie potrzeba żadnego innego kodu obsługującego zakładki, ponieważ cała obsługa interakcji z użytkownikiem jest przeprowadzana w tym obiekcie (przy pomocy akcji). Ponadto każdy obiekt reprezentujący zakładkę będzie automatycznie ładowany wraz z sesją, więc nie trzeba rozwiązywać problemu składowania tych obiektów.

Sesje w TLCC mogą trwać dłużej niż pojedynczy cykl pracy użytkownika z aplikacją. Serwis może wymagać logowania i po pomyślnym ustaleniu tożsamości użytkownika pozwolić mu kontynuować wcześniejszą sesję. Dzięki takiemu podejściu nie trzeba jawnie zachowywać wszystkich danych użytkownika (na wypadek gdyby opuścił sesję) i odtwarzać ich w nowej sesji po zalogowaniu. Do przechowywania sesji wystarczy jedna zmienna dzielona, na której znajduje się słownik użytkowników: kluczami byłyby identyfikatory, a wartościami główne obiekty użytkowników. W momencie logowania przy pomocy wbudowanej funkcji set_root() można by podmienić korzeń wyświetlania.

2 Sesje w PHP

Dzięki temu, że PHP pozwala zachowywać poszczególne zmienne między kolejnymi żądaniami użytkownika, obsługa sesji jest znacznie ułatwiona. W przypadku zakładek można spróbować zastosować podobne podejście co w TLCC. Zakładkę można zdefiniować przy pomocy klasy i obiekty tej klasy przechowywać na liście, która byłaby zapamiętana na specjalnej zmiennej. Jednak pewnym problemem byłoby w takim przypadku podłączanie się do wcześniej utworzonej sesji, przy logowaniu użytkownika. Obsługa tego zadania nie może się znaleźć wyłącznie w kodzie PHP, ponieważ potrzebujemy listy identyfikatorów sesji dla użytkowników, która byłaby dostępna ze wszystkich uruchamianych skryptów PHP. Zatem trzeba skorzystać z bazy danych i stworzyć specjalną tabelę, która spełniałaby funkcje analogiczne do słownika użytkowników, wykorzystanego w TLCC.

Innym problemem jest tendencja takich sesji do zmiany wielkości. Użytkownicy mogą systematycznie dodawać nowe zakładki do swoich stron, czego efektem będzie stały wzrost wielkości wszystkich sesji. Ponieważ PHP zapamiętuje dane każdej sesji w osobnym pliku, więc w przypadku dużej liczby użytkowników może to spowodować fragmentację - praktycznie każdy następny blok takiego pliku będzie oddalony od poprzedniego. Przypuszczenia te znalazły potwierdzenie w testach, opisanych w rozdziale 6. Wynika to ze sposobu, w jaki przydzielane są kolejne bloki w systemie plików Ext2 [4].

Gdybyśmy chcieli napisać kod całego serwisu w sposób obiektowy przy użyciu PHP, prawdopodobnie wystąpiłyby problemy związane z poprawnością takiego kodu. Ponieważ w PHP nie ma deklaracji zmiennych i ich typów, przeciążania funkcji składowych, a dostęp do atrybutów obiektu musi się odbywać przy pomocy słowa kluczowego this, więc pisanie obiektowego systemu nie jest zbyt wygodne.

3 Sesje w Javie

W serwletach Javy programista ma do dyspozycji wbudowane sesje, związane z otrzymywanymi żądaniami HTTP. Jednak sesje te mają następujące wady:

Zatem wbudowane sesje w Javie nie nadają się do przechowywania wszystkich danych użytkownika w serwisie (wraz z całym drzewem obiektów reprezentujących serwis).

Dla wcześniejszego przykładu związanego z tworzeniem zakładek przez użytkownika wymagane jest zatem umieszczenie wszystkich informacji w bazie danych. Ponieważ Java jest językiem obiektowym, więc najlepszym rozwiązaniem będzie napisanie klas pośredniczących między aplikacją, a bazą danych. Obiekty tych klas mogłyby symulować prawdziwe, trwałe obiekty, lecz ich stworzenie wymaga trochę pracy.

Zatem w stosunku do Javy język TLCC ma przewagę jeśli chodzi o łatwość zarządzania sesją. W odróżnieniu od Javy nie trzeba w nim pisać żadnego kodu, który zapewnia trwałość obiektów między kolejnymi żądaniami użytkownika - trwałość ta jest zapewniana automatycznie.

7 Skalowalność aplikacji

1 Skalowalność w TLCC

Dzięki możliwości uruchomienia wielu serwerów obiektów, aplikacja napisana w TLCC dobrze się skaluje. Każdy taki serwer zarządza pewną grupą sesji użytkowników i zawsze istnieje możliwość uruchomienia dodatkowych serwerów obiektów. Nowe sesje przydzielane są w taki sposób, by obciążenie serwerów było równomierne. Przy tym nie trzeba umieszczać w programie żadnego dodatkowego kodu obsługującego rozproszone przetwarzanie żądań.

Analogicznie wygląda sprawa dzielenia obciążenia między serwery obiektów dzielonych. Można uruchomić wiele takich serwerów i zapewnić równomierne rozmieszczenie danych. Wystarczy przy tworzeniu nowego obiektu zaznaczyć, że można go umieścić na dowolnym serwerze obiektów dzielonych. Można to zrobić przy pomocy specjalnego operatora new_shared, który wysyła żądanie utworzenia obiektu do najmniej obciążonego serwera.

W aplikacji opisywanej w tym rozdziale poszczególne grupy tematyczne mogą się znajdować na dowolnych maszynach. Zatem utworzenie nowej grupy podrzędnej dla bieżącej grupy może wyglądać następująco:

nodeData.topicNode = new_shared TopicNode(currentTopicNode); 
 
Dzięki zastosowaniu systemu CORBA przy odwołaniach do obiektu nie trzeba się martwić o to, na której maszynie został on stworzony. Wszystkie potrzebne dane znajdują się w referencji do tego obiektu.

2 Skalowalność w PHP

Ponieważ PHP automatycznie przydziela identyfikatory sesji, więc w przypadku równoważenia obciążenia serwisu trzeba ustalić jaki komputer powinien obsłużyć nadchodzące żądanie. Zatem wymagany jest pewien dodatkowy program, który na podstawie informacji o stworzonych sesjach (zapisanych w bazie danych) będzie rozsyłał żądania do odpowiednich maszyn.

Innym rozwiązaniem mogłoby być dołączenie stosownej informacji do wysłanego pakietu HTTP (np. w ciasteczku). Lecz nadal istnieje konieczność zapisania informacji o komputerze obsługującym sesję w bazie danych. W przypadku wielokrotnego korzystania z tej samej sesji (poprzez logowanie) należałoby tą informację odczytać. Nadal też trzeba skorzystać z programu rozsyłającego nadchodzące żądania do właściwych maszyn.

Natomiast w przypadku danych dzielonych pojawia się problem. Można uruchomić wiele baz danych (najlepiej darmowych, jak np. MySQL) na różnych maszynach, lecz administracja tak wieloma bazami byłaby dość kłopotliwa. Przy takim podejściu można by przechowywać poszczególne tabele w różnych bazach. Lecz rozdzielenie danych z jednej tabeli (np. informacji o grupach tematycznych) byłoby bardziej skomplikowane. Wtedy oprócz identyfikatorów samych grup trzeba by zapamiętać identyfikator maszyny, na której przechowywana jest ta grupa. Jednak takie rozwiązanie nie pozwala wykonywać zapytań odnoszących się do całego zbioru grup, jak np. pobranie wszystkich podkategorii dla konkretnej kategorii.

Zatem wymagane jest w takim przypadku zastosowanie jakiejś rozproszonej bazy danych (np. Oracle). Jednak koszt takiego systemu jest bardzo duży. Ponadto przewagą TLCC jest to, że wszelkie operacje na rozproszonych obiektach można wykonywać bezpośrednio, bez konieczności tworzenia specjalnych zapytań.

3 Skalowalność w Javie

Problem rozproszonego przetwarzania żądań w przypadku Javy rozwiązuje się bardzo podobnie jak w PHP. Konieczny jest pewien specjalny program, który na podstawie identyfikatora sesji, zawartego w żądaniu, przekieruje je do właściwej maszyny. Funkcjonalność taką zapewniają niektóre kontenery serwletów, jak np. Tomcat. Jednak sama konfiguracja Tomcata i rozmieszczenie źródeł są znacznie bardziej skomplikowane niż w PHP. Odróżnia to kontenery serwletów także od TLCC, w którym cała konfiguracja przetwarzania żądań sprowadza się do edycji jednego, prostego pliku.

Natomiast jeśli chodzi o rozproszone przechowywanie danych dzielonych między sesjami, to sytuacja wygląda identycznie jak w przypadku PHP (ponieważ również korzystamy z bazy danych).

8 Podsumowanie

W tej części znajduje się zestawienie cech różniących język TLCC od PHP i Javy. Wymienione zostały wszystkie zalety TLCC związane z łatwością i szybkością tworzenia aplikacji WWW. Natomiast ostatni punkt omawia wady TLCC.

1 Porównanie z PHP

Oto argumenty świadczące o tym, że tworzenie serwisów WWW w PHP jest bardziej złożone niż w proponowanym przeze mnie języku.

  1. W PHP konieczne jest wykorzystanie bazy danych do przechowywania informacji w sposób trwały. Chociaż w niektórych sytuacjach informacje można zapamiętywać w sesjach PHP, to jednak w przypadku wielu użytkowników i zmieniających się rozmiarów sesji rozwiązanie to będzie zbyt wolne. Użycie bazy danych powoduje konieczność administrowania nią, tworzenia tabel, sekwencji oraz wykonywania zapytań SQL w kodzie aplikacji. Natomiast w TLCC można bezpośrednio odwoływać się do konkretnych obiektów, ponieważ wszystkie tworzone obiekty są trwałe.
  2. Umieszczenie w kodzie aplikacji zapytań SQL wymaga napisania specjalnych klas (lub jedynie funkcji), które będą komunikowały się z bazą. Jest to związane z dodatkową pracą oraz jest źródłem błędów, gdyż programista musi budować skomplikowane napisy, które składają się z instrukcji SQL. Poprawność takiego napisu może być zweryfikowana dopiero w momencie wysłania go do bazy. Ponadto zapytanie może być niepoprawne tylko w niektórych przypadkach (zależnie od wartości pewnych zmiennych), co utrudnia odpluskwianie programu.
  3. Rozpoznając akcje użytkowników, w PHP trzeba rozdzielić fragment kodu, który daną akcję obsługuje i część wyświetlającą odpowiedni odnośnik. Ponadto sam odnośnik jest konstruowany przez programistę, który musi pamiętać o zapisaniu w nim identyfikatora akcji i parametrów. Nie ma przy tym żadnej kontroli zgodności argumentów dostarczonych w odnośniku z tymi, których wymaga funkcja obsługi akcji. W przypadku wielu różnych akcji kod programu będzie mało przejrzysty.
  4. Natomiast obsługując formularze do przekazywania dodatkowych parametrów akcji należy posłużyć się specjalnymi polami ukrytymi. Ponadto wartości pól wprowadzone przez użytkownika są widoczne jako zmienne globalne, których identyfikatory odpowiadają nazwom tych pól. Zatem obsługa formularzy w PHP jest bardziej złożona niż w TLCC, w którym zmienne dla pól oraz samą akcję można podać bezpośrednio w definicji formularza.
  5. Rozproszona obsługa żądań HTTP wymaga dodatkowego kodu, który zarządza rozsyłaniem ich do właściwych serwerów. Nie ma jednak możliwości przechowywania danych dzielonych w środowisku rozproszonym bez dodatkowego nakładu pracy. Natomiast w TLCC możliwości te wynikają z zastosowania systemu CORBA.
  6. W przypadku stworzenia dużego serwisu w PHP przy użyciu obiektów, kod będzie raczej wolny i mało czytelny - PHP jest językiem skryptowym, nie wymaga deklaracji zmiennych, nie generuje błędów kompilacji. Niektóre konstrukcje PHP są niewygodne, jak np. konieczność odwoływania się do atrybutów obiektu przy pomocy wskaźnika this, czy używanie referencji. Ponadto jest to język znacznie wolniejszy od C++ (a więc także od TLCC).
  7. PHP jest zorientowany na wypisywanie kodu HTML, a nie na wykonywanie instrukcji. Aby bezpośrednio wstawić blok HTML wewnątrz kodu PHP trzeba najpierw zamknąć znacznik PHP ('?>'), a następnie z powrotem go otworzyć ('<?'). Natomiast język TLCC dzięki możliwości wielokrotnego zagnieżdżania bloków poszczególnych rodzajów pozwala tworzyć czytelny kod o widocznej strukturze (w PHP umieszczenie znacznika '?>' nie zamyka bieżącego bloku instrukcji).

2 Porównanie z Javą

Lista stanowi cechy różniące Javę i TLCC.

  1. Podobnie jak w przypadku PHP użycie bazy danych i konstruowanie zapytań SQL w kodzie programu jest kłopotliwe. Ponadto musimy zadbać o administrację bazą, a także napisać specjalne klasy, których zadaniem będzie pośredniczenie między aplikacją a bazą danych.
  2. Użycie systemu CORBA do przechowywania danych wiąże się z dodatkowym nakładem pracy. Niezbędna jest specjalna biblioteka, analogiczna do tej, z której korzystają programy generowane przez translator TLCC.
  3. Nawet w przypadku stworzenia specjalnej biblioteki w Javie jej użycie nie byłoby wygodne. Należałoby jawnie korzystać z mechanizmów udostępnianych przez system CORBA. Ponadto odczuwalny byłby brak specjalnych typów zmiennych (dzielonych i statycznych w obrębie sesji). Fragmenty biblioteki, które w przypadku TLCC są dynamicznie generowane (ponieważ są zależne od klas użytych w końcowej aplikacji) w Javie muszą być napisane ręcznie.
  4. W Javie nie ma przezroczystego mechanizmu zarządzania sesją. Informacje umieszczane w obiekcie reprezentującym sesję muszą być jawnie odczytywane przy każdym żądaniu. Ponadto dane te przechowywane są bezpośrednio w pamięci.
  5. Obsługa akcji i formularzy, tak jak w PHP, wiąże się z koniecznością generowania odpowiednich odnośników lub pól ukrytych. Trzeba także napisać funkcje rozpoznające wywoływane akcje, których implementacja będzie oddzielona od samego odnośnika. Aby pobrać parametry żądania HTTP trzeba posługiwać się specjalnymi obiektami Javy i często stosować konwersje typów, co nie jest wygodne.
  6. W Javie brakuje mechanizmu łatwego umieszczania kodu HTML wewnątrz funkcji. Trzeba doklejać do łańcucha kolejne wiersze kodu strony WWW, co jest niewygodne. Rozwiązanie tego problemu wymaga stosowania specjalnych narzędzi, jak np. system szablonów WebMacro.
  7. Program w Javie jest wolniejszy niż w C++. W szczególności odnosi się to do systemu CORBA, który wymaga wysokiej wydajności języka programowania i jego implementacja w Javie działa wolniej niż w C++.

3 Ograniczenia TLCC

W przypadku aplikacji, która prezentuje użytkownikowi przechowywane dane w wielu różnych ujęciach (np. wyświetla złożone statystyki) łatwiejsze w implementacji może być użycie zapytań SQL, niż różnych struktur danych przechowujących obiekty. Jednak zapytania te często też są złożone, a ponadto czas ich wykonywania w razie dużej ilości danych w bazie jest długi. Poza tym wykorzystanie skomplikowanych wyrażeń SQL w typowej aplikacji WWW jest rzadkie. Przykładowy serwis, opisywany w tym rozdziale nie wymagał stosowania takich wyrażeń.

Jednak w pewnych (rzadkich) sytuacjach użyteczne byłoby skorzystanie ze specjalnej funkcji do wykonywania globalnych zapytań, które dotyczą wszystkich obiektów w systemie. Projekt takiego rozszerzenia znajduje się w dodatku B. Użycie tej funkcji przypominałoby wykonanie zapytania do bazy danych w programach napisanych w PHP lub Javie, jednak byłoby nieco łatwiejsze w użyciu.


next up previous contents
Next: 6 Testy Up: Język do tworzenia aplikacji Previous: 4 Implementacja programu   Spis rzeczy
Paweł Lenk 2002-12-10