PASOE i serwisy WebHandler cz. II

Reasumując to co można było prześledzić w poprzednim artykule, warstwa transportowa WEB jest związana z interfejsem biznesowym podobnym do REST. Wykorzystuje te same żądania (czasowniki) HTTP (GET, PUT, POST,…). Istotna różnica to przechwytywanie obsługi żądań przez WebHandler, czyli klasę OO ABL. Dzięki temu unika się żmudnego mapowania, a deweloperzy dostają duże możliwości dopasowania obsługi do własnych potrzeb. Dostęp do serwisu jest przez URI, w którym zamiast warstwy REST podaje się WEB oraz nazwę WebHandlera, który obsługuje dany serwis np: webh1/web/webh1.
Webh1 jest tutaj nazwą serwisu, WEB wartswą transportową, a następne wystąpienie webh1, WebHandlerem (domyślny WebHandler ma tę samą nazwę jak serwis).

W drugim przykładzie (także opartym na bazie wiedzy) pokażę jak zdefiniować w projekcie/serwisie dodatkowe URI i jak zrealizować dostęp do 2 obiektów business entity.

Utwórzmy kolejny projekt webh2, typ Server i warstwa transportowa WEB. Projekt jest połączony z baza sports2000. Kto nie wie jak to zrobić niech zajrzy do części I.

Definiujemy business entity: customerBE dla tablicy customer i orderBE dla tablicy order. Zostają utworzone dwie klasy customerBE.cls oraz orderBE.cls (a także pliki .i) – patrz poniżej.

Teraz klikamy prawym klikiem myszy na nazwę serwisu i wybieramy Edit oraz przycisk Add.


Dodajemy URI: /Customer oraz /Orders.

Modyfikujemy plik klasy webh2Handler.cls aby miał następującą postać (dokładniej, modyfikowana jest metoda HandleGet):

USING Progress.Lang.*.
USING OpenEdge.Web.WebResponseWriter.
USING OpenEdge.Net.HTTP.StatusCodeEnum.
USING OpenEdge.Web.WebHandler.
USING Progress.Json.ObjectModel.*.
USING customerBE.*.
USING orderBE.*.  

BLOCK-LEVEL ON ERROR UNDO, THROW.

CLASS webh2Handler INHERITS WebHandler: 

 {"customerbe.i"}
 {"orderbe.i"}   	
		
	/*------------------------------------------------------------------------------
            Purpose: Handler for unsupported methods. The request being serviced and
            		 an optional status code is returned. A zero or null value means 
            		 this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotAllowedMethod( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
		/* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */
        	
		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
	END METHOD.


	/*------------------------------------------------------------------------------
            Purpose: Handler for unknown methods. The request being serviced and an 
                     optional status code is returned. A zero or null value means 
                     this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotImplemented( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
		/* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */	
		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
   	END METHOD.
 	
	
	/*------------------------------------------------------------------------------
            Purpose: Default handler for the HTTP GET method. The request being 
                     serviced and an optional status code is returned. A zero or 
                     null value means this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
 	METHOD OVERRIDE PROTECTED INTEGER HandleGet( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
 	
	
		DEFINE VARIABLE oResponse AS OpenEdge.Net.HTTP.IHttpResponse NO-UNDO.
        DEFINE VARIABLE oWriter   AS OpenEdge.Web.WebResponseWriter  NO-UNDO.
        DEFINE VARIABLE oBody     AS OpenEdge.Core.String            NO-UNDO.
       
        DEFINE VARIABLE jsonObj AS JsonObject NO-UNDO.
        DEFINE VARIABLE lcJSON AS LONGCHAR NO-UNDO.

        ASSIGN 
            oResponse            = NEW OpenEdge.Web.WebResponse()
            oResponse:StatusCode = INTEGER(StatusCodeEnum:OK).
        

        jsonObj = NEW JsonObject().
        
        IF ENTRY(2,poRequest:PathInfo,"/") = "Customer" THEN
        DO: 
            DEFINE VARIABLE beCustomer AS customerBE NO-UNDO.
            DEFINE VARIABLE hTTCustomer AS HANDLE NO-UNDO.
            
            beCustomer = NEW customerBE().
            beCustomer:ReadcustomerBE("",OUTPUT DATASET dsCustomer).
            hTTCustomer = TEMP-TABLE ttCustomer:HANDLE.
            hTTCustomer:WRITE-JSON ("JsonObject",jsonObj).
            lcJSON= jsonObj:GetJsonText().
            oBody = NEW OpenEdge.Core.String(lcJSON).
                      
            ASSIGN    
                oResponse:Entity = oBody
                oResponse:ContentType   = 'application/json':u
                oResponse:ContentLength = oBody:Size.
        END.
        ELSE
           IF ENTRY(2,poRequest:PathInfo,"/") = "Orders" THEN
           DO:
            DEFINE VARIABLE beOrder AS orderBE NO-UNDO.
            DEFINE VARIABLE hTTOrder AS HANDLE NO-UNDO.
            
            beOrder = NEW orderBE().
            beOrder:ReadOrderBE("",OUTPUT DATASET dsOrder).
            hTTOrder = TEMP-TABLE ttOrder:HANDLE.
            hTTOrder:WRITE-JSON ("JsonObject",jsonObj).
            lcJSON= jsonObj:GetJsonText().
            oBody = NEW OpenEdge.Core.String(lcJSON).
                      
            ASSIGN 
                oResponse:Entity = oBody
                oResponse:ContentType   = 'application/json':u
                oResponse:ContentLength = oBody:Size.
          END.
          ELSE
          DO:
            ASSIGN
                oBody = NEW OpenEdge.Core.String(
                                 'Hello '
                               + '~r~n':u   /*CRLF */
                               + 'This is the default message by th HandleGet in webh2Handler.'
                               ).
            ASSIGN
                oResponse:Entity        = oBody
                oResponse:ContentType   = 'text/plain':u
                oResponse:ContentLength = oBody:Size.                               
         END.
       
        ASSIGN 
            oWriter = NEW WebResponseWriter(oResponse).
        oWriter:Open().
        
        oWriter:Close().
        
        RETURN 0.
		
 	END METHOD. 
 	 	   	
	
END CLASS.

Po uruchomieniu serwera PASOE możemy przetestować działanie serwisu.
Wpisujemy w przeglądarce URL dla danych z tablicy Customer: http://localhost:8810/webh2/web/Customer.

Następnie dla Orders: http://localhost:8810/webh2/web/Orders.

A na końcu testujemy domyślny WebHandler: http://localhost:8810/webh2/web/webh2.

Jeszcze wspomnę, że dodawać WebHandlery i przypisywać do nich URI można na różne sposoby.
Jednym z nich, choć niepolecanym, jest plik konfiguracyjny instancji serwera aplikcaji openedge.properties (podkatalog conf).

O wiele lepszym sposobem jest interfejs webowy OpenEdge Explorer.
Wybieramy: Progress Application Server -> oepas1 -> ABL Application: oepas1 -> ABL WebApp: webh2 -> WEB Transport Configuration

PASOE i serwisy WebHandler cz. I

Deweloperzy aplikacji webowych z wykorzystaniem serwera PASOE mają od wersji 11.6.3 nową możliwość tworzenia serwisów typu Data Object. Są to tzw. WebHandlery, które działają z wykorzystaniem warstwy transportowej WEB (Data Object WebHandler Service).

WebHandler zapewnia bardziej wydajną i prostszą warstwę komunikacyjną niż REST. Jest napisany w ABL, łatwiej dopasować go do własnych potrzeb i ma ulepszone możliwości debugowania. Podstawową zaletą WebHandlera jest to, że daje programistom pełną kontrolę nad przychodzącymi i wychodzącymi danymi.

Warstwa WEB obsługuje żądania (requests) i odpowiedzi (responses), które używają standardowych czasowników HTTP (verbs). Obejmuje to interakcje z klientami, takimi jak WebSpeed i OpenHTTP.
Jeśli chcesz zmienić domyślny adres URL, możesz dodać dodatkowe WebHandlery i zmapować je na różne adresy URL lub zmienić odwzorowania domyślnych WebHandlerów.

Dodatkowe WebHandlery można dodać w pliku openedge.properties dla instancji OEPAS i zmapować je na określone adresy URL. Na przykład:

defaultHandler=OpenEdge.Web.CompatibilityHandler
webhandler1=MyHandler:/mycustomer
webhandler2=MyHandler:/mycustomer/{custid}

OpenEdge.Web.CompatibilityHandler zapewnia kompatybilność z aplikacjami WebSpeed SpeedScript i CGI Wrapper. Jest to domyślny handler używany w instancji w środowisku programistycznym.

Tworzenie tych serwisów zilustruję dwoma przykładami zaczerpniętymi z bazy wiedzy.

Zaczynamy od założenia, że mamy instancję serwera PASOE z podłączoną bazą danych sports2000.
Tworzymy nowy projekt OpenEdge. Nadajemy mu nazwę np. webh1, wybieramy typ Server i zaznaczamy warstwę transportową WEB.

Klikamy Next, sprawdzając czy do projektu jest podłączona instancją serwera aplikacji, a w ostatnim kroku upewniamy się, że jest przyłączona nasza baza danych.

Kreator tworzy od razu klasę WebHandlera, w tym przypadku webh1Handler.cls, zawierającą 3 metody: HandleNotAllowedMethod, HandleNotImplemented oraz HandleGet. Dwie pierwsze są związane z wychwytywaniem błędów. HandleGet to główna metoda, w której można przetwarzać dane otrzymane z odpowiedzi (response).

Stworzymy teraz obiekt danych. W Project Explorerze zaznaczamy nazwę projektu i prawym klikiem myszy wybieramy New -> Business Entity.


Podajemy nazwę customerBE, klikamy Next i wybieramy z bazy tabelę customer.

W efekcie jest utworzona znana z poprzednich artykułów klasa customerBE.cls.
Do klasy tej będziemy odnosić się z WebHandlera, czyli pliku webh1Handler.cls. Teraz należy zmodyfikować metodę HandleGet aby miała postać tak jak w poniższej klasie webh1Handler.cls (można skopiować cały plik klasy).

Widać, że oprogramowanie metod wymaga znajomości programowania obiektowego i odpowiednich referencji, ale daje bardzo duże możliwości. W poniższym przykładzie w odpowiedzi HTTP pobierana jest zawartość tablicy ttCustomer (z obiektu customerBE) i wyświetlana poprzez obiekt JSON.

USING Progress.Lang.*.
USING OpenEdge.Web.WebResponseWriter.
USING OpenEdge.Net.HTTP.StatusCodeEnum.
USING OpenEdge.Web.WebHandler.
USING customerBE.*.
USING Progress.Json.ObjectModel.*.

BLOCK-LEVEL ON ERROR UNDO, THROW.

CLASS webh1Handler INHERITS WebHandler: 

	    {"customerbe.i"}
		
	/*------------------------------------------------------------------------------
            Purpose: Handler for unsupported methods. The request being serviced and
                     an optional status code is returned. A zero or null value means 
            	     this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotAllowedMethod( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
        /* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */
        	
		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
	END METHOD.


	/*------------------------------------------------------------------------------
            Purpose: Handler for unknown methods. The request being serviced and an 
                     optional status code is returned. A zero or null value means 
                     this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotImplemented( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
	/* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */	

		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
   	END METHOD.
 	
	
	/*------------------------------------------------------------------------------
            Purpose: Default handler for the HTTP GET method. The request being 
                     serviced and an optional status code is returned. A zero or 
                     null value means this method will deal with all errors.                                                           
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
 	METHOD OVERRIDE PROTECTED INTEGER HandleGet( INPUT poRequest AS OpenEdge.Web.IWebRequest ): 	

	DEFINE VARIABLE oResponse AS OpenEdge.Net.HTTP.IHttpResponse NO-UNDO.
        DEFINE VARIABLE oWriter   AS OpenEdge.Web.WebResponseWriter  NO-UNDO.
        DEFINE VARIABLE oBody     AS OpenEdge.Core.String            NO-UNDO.
             
        DEFINE VARIABLE beCustomer AS customerBE NO-UNDO.
        DEFINE VARIABLE pcFilter AS CHAR NO-UNDO.
        DEFINE VARIABLE lcJSON AS LONGCHAR NO-UNDO.

        DEFINE VARIABLE jObj AS JsonObject.
        DEFINE VARIABLE htt AS HANDLE.
        
        jObj = NEW JsonObject().
        /* Get data from the BE */
        beCustomer = NEW customerBE().
        beCustomer:ReadcustomerBE(INPUT pcfilter,OUTPUT DATASET dsCustomer).
        htt = TEMP-TABLE ttCustomer:HANDLE.
        htt:WRITE-JSON("JsonObject", jObj).

        ASSIGN 
            oResponse            = NEW OpenEdge.Web.WebResponse()
            oResponse:StatusCode = INTEGER(StatusCodeEnum:OK)
                        .
        /* This body object can be a string or something else (JsonObject for instance) */
        lcJSON= jObj:GetJsonText().
        ASSIGN 
            oBody = NEW OpenEdge.Core.String(lcJSON).            
                              
        ASSIGN 
            oResponse:Entity        = oBody
            /* HTTP messages require a content type */
            oResponse:ContentType   = 'application/json':u
            /* ContentLength is good too */
            oResponse:ContentLength = oBody:Size
            .
        
        /* The WebResponseWriter ensures that the status line and
           all headers are writted out before the message body/entity. */
        ASSIGN 
            oWriter = NEW WebResponseWriter(oResponse).
        oWriter:Open().
        
        /* Finish writing the response message */
        oWriter:Close().
        
        /* A response of 0 means that this handler will build the entire response;
           a non-zero value is mapped to a static handler in the webapp's /static/error folder.
           The mappings are maintained in the webapps's WEB-INF/web.xml 
           A predefined set of HTTP status codes is provided in the OpenEdge.Net.HTTP.StatusCodeEnum 
           enumeration */
        RETURN 0.
		
 	END METHOD. 
 		
END CLASS.

W przeglądarce wpisujemu URL dla naszej instancji PASOE np.: http://localhost:8810/webh1/web/webh1 (lub https://localhost:8811/webh1/web/webh1 dla bezpiecznego połączenia).
W efekcie dostajemy ekran podzielony na 3 przekładki:
JSON

Raw Data

Headers

Change Data Capture cz. II

Kontynuujemy temat związany z praktycznym wykorzystaniem technologii Change Data Capture opisanej w poprzednim artykule.

Mamy zatem bazę danych myCDC z włączoną funkcją CDC i zdefiniowanymi obszarami dla danych i indexów CDC.

Wejdżmy jeszcze raz w OE Explorerze w opcję Database Administration.

W sekcji Storage Management znajdziemy opcje, w których możemy zmodyfikować listę tabel lub stworzyć/zmodyfikować zasady CDC (CDC policy).
W sekcji Data Administration znajdują się opcje dla zrzucenia lub załadowania zdefiniowanych zasad CDC.

Podczas tworzenia zasad CDC należy określić poziom (Level), który określa ilość zapisywanych danych. Ilustruje to poniższa tabela.

Poziom Opis Fieldmap Czy można zmienić poziom?
 0  Przechowywanie danych tylko w Tracking Table  Brak  Nie
 1   Przechowywanie danych tylko w Tracking Table. Zawiera Fieldmap  Odzwierciedla zmienione pola tylko dla aktualizacji  Tak
 2  Zapis aktualnych wartości (after) dla wszystkich operacji CUD (Create, Update, Delete)   Odzwierciedla zmienione pola tylko dla aktualizacji  Tak
 3   Zapis aktualnych wartości (after) oraz poprzednich (before) dla wszystkich operacji CUD
  Odzwierciedla zmienione pola tylko dla aktualizacji  Tak

Pole Fieldmap jest wykorzystywane wtedy gdy interesuje nas, które pola zastały zmienione, ale nie interesują nas same wartości.

Utwórzmy teraz zasadę CDC poziom 2.

Na stronie Data Administration, w sekcji Storage Management, wybieramy Create Change Data Capture policy.

Wprowadzamy dane: Policy name: CustomerPolicy, Table: PUB.Customer, Level: Medium(2). Wartość State zostawiamy jako Inactive. Zasadę uaktywnimy później.

W polach Data area i Index area podajemy nazwy zdefiniowanych w bazie obszarów, jak na powyższym rysunku.

Jeśli nie podamy wartości Change table, to przyjmie ono domyślna nazwę, tutaj: CDC_Customer. Wartośc pola Change table owner przyjmie wartość pub.

Jeśli chcemy wybrać pola, których wartości mają być przechwytywane, zaznaczamy Identifying fields.

Z listy poniższych pól wybieramy: City, Country, CustNum, Name (zaznaczamy checkbox w pierwszej kolumnie). Dla pola CustNum w kolumnie Enable identifying field wybieramy YES, a wartość pola Field order ustawiamy 1. Dla tego pola zostanie utworzony index w tabeli Change Table.

Przyciskiem SUBMIT (na górze strony) tworzymy zasadę CDC.

Powinniśmy teraz widzieć poniższy ekran.

Wybieramy w górnym menu: Database Administration i Go to Database Administration.

Widzimy zdefiniowaną zasadę CDC dla tabeli Customer. W kolumnie Policy state widać No Current Policy ponieważ nie uaktywniliśmy jeszcze tej zasady. Klikamy na nazwę CustomerPolicy w polu Pending policy.

Zaznaczamy Active i przycisk SUBMIT.

Zasada CustomerPolicy jest już aktywna. Zasadę mogliśmy uaktywnić oczywiście wcześniej, podczas definiowania.

Pora napisać jakiś program. Otwieramy klienta podłączonego do bazy myCDC (np. komenda prowin myCDC) i uruchamiamy “skomplikowany” program składający się z instrukcji CREATE customer.

Następnie uruchamiamy program Customer_CTT.p oparty na schemacie z tablicy Change Tracking Table.

// Customer_CTT.p

 FOR EACH _file, each _cdc-change-tracking WHERE _file._file-number = 
          _cdc-change-tracking._source-table-number AND _file._file-name="customer":
  CASE _operation:

    WHEN 1 THEN
      DISPLAY "Create"  _operation  _file._FILE-NAME.
    WHEN 2 THEN  
      DISPLAY "Delete"  _operation  _file._FILE-NAME.
    WHEN 3 THEN  
      DISPLAY "Before Update"  _operation  _file._FILE-NAME.
    WHEN 4 THEN  
      DISPLAY "After Update" _operation  _file._FILE-NAME.
  END CASE.
END.

Widzimy informację, że została wykonana operacja 1 (Create) i utworzony został jeden rekord Customer.

W drugim przykładzie Customer_CT.p korzystamy z danych zapisanych do Change table CDC_Customer.

// Customer_CT.p

FOR EACH cdc_customer :
  DISPLAY _operation country city custNum name.
end.

W Change table zapisywane są jedynie pola wybrane podczas tworzenia zasady (wartości pól CustNum i Country ustawiane są przez tryger Create).

Wprowadźmy następną zmianę w danych.np. pole name dla pierwszego Customera = “Lift Tours Corp.”.

Customer_CTT.p

Mamy tu już dwie operacje na tablicy Customer: Create i After Update.

Customer_CT.p

Widzimy nową wartość pola Name oraz wartość CustNum (jest to Identifying field). Wartości pozostałych pól nie zmieniły się i są wyświetlone jako ?.

Powyższe przykładowe proste programy ilustrują sposób odczytu zmodyfikowanych danych dla zasady na poziomie 2. Poziom można ustawić w zależności od potrzeb. Tak uzyskane dane moga słuzyć różnym celom. Może to być element systemu ETL (o czym wspomniałem w pierwszej części) czy np. naszego własnego systemu do replikacji wybranych informacji.

Change Data Capture cz. I

Progress OpenEdge Change Data Capture (CDC) jest mechanizmem śledzącym, który umożliwia aplikacjom zaimplementowanie procesu, który automatycznie rejestruje zmiany w tabelach użytkowników w bazie danych OpenEdge.

Obsługuje on również automatyczne kopiowanie podzbiorów danych, które uległy zmianie do specjalnych tablic zmian (Change Tables). Zebrane przez CDC dane mogą być wykorzystane przez narzędzia wspomagające procesy ETL (Extract, Transform, Load).

Jednym z przykładówich ich wykorzystania jest identyfikacja trendów na podstawie danych w hurtowni danych, która została zapełniona informacjami z CDC change tables przy użyciu procesu ETL.

Change Data Capture to nowy produkt w OE 11.7. Można go kupić oddzielnie lub razem z licencją OE Advance Enterprise RDBMS. Funkcje wspierające CDC można znaleźć w OpenEdge SQL, OpenEdge ABL, OpenEdge Management, OpenEdge Explorer i w innych obszarach.

Dane dostarczane przez proces przechwytywania znajdują się w źródłowej bazie danych i są przechowywane w formie relacyjnej.

OpenEdge CDC jest elastyczny, ponieważ:

  • Przechwycone dane są przechowywane w tej samej bazie danych
  • Przechwycone dane są utrzymywane w formie relacyjnej
  • Przechwycone dane są dostępne poprzez składnie SQL i ABL.

OpenEdge CDC jest skalowalny, ponieważ można zdefiniować zasady (policy) CDC takie, że:

  • Ilość przechwyconych danych może być różna dla różnych tabel. Można nie przechwytywać żadnych danych, wybrane dane lub cały rekord
  • Można indeksować dane w celu łatwiejszego wyszukiwania informacji
  • Ilość przechwyconych danych jest kontrolowana za pomocą zasad zdefiniowanych na poziomie tabeli i pola.

Niektóre zalety OpenEdge Change Data Capture to:

  • Identyfikacja i śledzenie wszystkich zmian danych w bazie OpenEdge
  • Gwarancja dokładności śledzenia wszystkich zmian danych bez względu na miejsce ich występowania
  • Zwiększenie efektywności i dostępności zmian dla ETL w celu synchronizacji określonych zmian z
    innymi źródłami danych, repozytoriami danych lub hurtowniami danych
  • Jedno miejsce konfiguracji niezależnie od ABL lub SQL
  • Możliwość aktywacji bez jakichkolwiek zmian w kodzie aplikacji, wystarczy tylko skonfigurować i uruchomić
  • Możliwość zarządzania całkowicie online – bez żadnych przestojów.

Włączenie mechanizmu CDC powoduje dodanie dwóch tabel do metaschematu bazy: _Cdc-Table-Policy oraz _Cdc-Field-Policy. Tabele te zawierają informacje o zasadach CDC dla tabel i powiązanych z nimi pól.

Oprócz tego dodawane są tabele: Change Tracking Table (_Cdc-Change-Tracking) i oddzielne tabele CDC dla każdej tabeli źródłowej, dla której chcemy przechwytywać zmiany danych (wspomniane wcześniej Change Tables).

Tabela _Cdc-Change-Tracking przechowuje zapis wszystkich działań związanych ze zmianami danych we wszystkich tablicach źródłowych, zgodnie z zasadami CDC. Przechwytywanie jest wykonywane poprzez specjalne trygery CDC, wbudowane w silnik bazy. Zawiera także informacje niezbędne do zabezpieczenia sekwencji transakcyjnej. Następnie dane te są zapisywane przez trygery do tabel Change Tables, o ile poziom zasad jest większy niż zero (w tym przypadku wszystkie dane znajdują się tylko w Change Tracking Table).

Przed włączeniem mechanizmu CDC należy przydzielić miejsce dla nowych tabel i indeksów. Ważnym zadaniem administratora jest monitorowanie przyrostu tych danych oraz zarządzanie nimi.

OK, po tym przydługawym wprowadzeniu zabierzmy się do pracy i stwórzmy bazę przygotowaną do CDC. Pamiętajmy, że aby CDC można było włączyć, baza musi zawierać obszary typu II.

Najpierw tworzymy nową bazę np. myCDC, kopię bazy sports2000 poleceniem:

prodb myCDC sports2000

Do bazy dodajemy obszary, w których będą przechowywane dane przechwycone przez CDC. Najpierw tworzymy plik add_CDC.st

#
d "CDC_Track_Data":20,64;512 .
#
d "CDC_Track_Index":21,1;64 .
#
d "CDC_Customer_Data":25,64;512 .
#
d "CDC_Customer_Index":26,1;64 .
#
d "CDC_Misc_Data":27,64;512 .
#
d "CDC_Misc_Index":28,1;64 .
#

Teraz uruchamiamy polecenie:

prostrct add myCDC add_CDC.st

którego efekt widać na poniższym obrazku.

Teraz skorzystamy z narzędzia webowego OE Explorer lub OE Management. Po zalogowaniu się wybieramy Resources -> Database. Pojawia się widok Database Migration Utility, w którym podajemy parametry utworzonej bazy myCDC wraz z numerem portu, np. 1005. Zaznaczamy Autostart database broker.

Po naciśnięciu SUBMIT powinien pojawić się poniższy widok.

Teraz w menu głównym klikamy Database Administration i Go to Database Administration.

Na liście baz danych klikamy naszą bazę myCDC.

Pojawia się ekran z kilkoma widokami. Po prawej stronie znajduje się widok Database Features, na której odnajdziemy wyłączona funkcję Change Data Capture.

Klikamy Enable.

Teraz musimy podać w jakich obszarach będą zapisywane dane i indeksy. Klikamy ikonki z lupą i wybieramy odpowiednie obszary.

Teraz klikamy Enable change data capture.

Powinien pojawić się poniższy komunikat.

Włączenie CDC możemy wykonać także bezpośrednio z linii komend np:

proutil myCDC -C enablecdc area Track_CDC_Data indexarea CDC_Track_Index

Na razie wystarczy. W następnym odcinku zdefiniujemy zasady CDC i napiszemy kilka prostych programów ilustrujących wykorzystanie informacji z change tables.

Tworzenie projektów w OE Developer Studio 11.7.2

Podczas kilku wcześniejszych artykułów pokazywałem jak utworzyć różne proste projekty w OpenEdge Developer Studio. Służyły one np. do wystawienia serwisów REST z wykorzystaniem obiektów JSDO i automatycznym generowaniem klas lub bardziej złożone z ręcznym mapowaniem parametrów. Przykłady te dotyczyły nowego serwera PASOE choć można było wykorzystać także klasyczny AppServer.

Takie tworzenie projektów odbywa się poprzez tzw. wizardy, których zadaniem jest “poprowadzenie za rękę” dewelopera, aby ułatwić mu życie. Jeśli idzie o intuicyjność wizardów, np. dla projektów typu OpenEdge, których jest kilka rodzajów, to pozostawiały one nieco do życzenia i bez znajomości zdobytej np. poprzez webinary lub dokukmentację łatwo można było popełnić błąd.

W ostatnich wersjach OpenEdge proces ten został poprawiony a jego klarowność jest zdecydowanie lepsza.

Tworząc nowy projekt typu OpenEdge od razu pojawia się okno (poniżej), w którym rodzaje projektów są pogrupowane w 3 grupy: Server, Client, General. Wybierając grupę i typ projektu w polu Description pojawia się krótki opis.


W grupie Server można zdecydować czy wykorzystany będzie serwer PASOE czy klasyczny AppServer.

Dla PASOE można dalej wybrać warstwę transportową: APSV, REST lub WEB.
Pierwsze dwie warstwy były omawiane na tym blogu. APSV służy do obsługi procedur ABL poprzez HTTP.
REST to projekt, w którym możemy wykorzystać wywołania RESTful API i ręcznie mapować wybrane pola. Jeśli zaznaczymy Create a DataObject Service, będzie to projekt z automatycznym mapowaniem struktur ProDataSet do JSDO.

WEB służy do utworzenia projektu z wykorzystaniem wywołań RESTful API przez klasę WebHandler. Ten rodzaj nie był jeszcze omawiany na blogu.

Jeśli zamiast PASOE wybierzemy klasyczny AppServer to będziemy miali do wyboru 2 rodzaje: APSV i REST.

Grupa Client to przede wszystkim projekty związane z budowaniem aplikacji desktopowej, może to być aplikacja GUI for .NET, z wykorzystaniem AppBuildera lub znakowa (ChUI). Można także budować interfejs webowy WEB UI lub klasyczny WebSpeed.

Grupa General, to podstawowy projekt oraz pozostałe z grupy OpenEdge, np. Dynamics lub (omawiany na Pizza & Beer) ABLUnit do przeprowadzania testów ABL. Można wreszcie stworzyć projekt Custom z konfiguracją dopasowaną do własnych potrzeb.

Procedury ABL i serwer aplikacji PAS cz.II

Kontynuując omawianie różnych zagadnień związanych z uruchamianiem aplikacji ABL na serwerze PAS zobaczmy jak osiągnąć połączenie w różnych trybach pracy, charakterystycznych dla klasycznego AppSerwera, co może być istotne podczas procesu migracji aplikacji.

W poprzedniej części zobaczyliśmy jak przyłączyć się do serwera PAS w modelu session-free i session-managed. Wybór modelu należy do klienta, a nie serwera aplikacji, którego pojedyncza instancja potrafi obsłużyć jednocześnie oba modele.

Dla session-free nie potrzebne są żadne zmiany w aplikacji poza samą instrukcją przyłączenia.

Dla session-managed mamy w AppSerwerze 3 tryby pracy:

  • state-reset
  • state-aware
  • stateless

Tryby te związane są z zarządzaniem kontekstem między żądaniami i wprowadzono je dla ułatwienia procesu programowania.

Przywiązanie procesu agenta do określonej sesji klienta powoduje zachowanie kontekstu, ale zmniejsza skalowalność. Różne tryby pracy dają więc programoście możliwość wyboru. Istotne jest, że tryby te w klasycznym AppSerwerze wybiera się podczas jego startu i nie mogą być zmienione jak długo serwer pracuje.

Jeśli więc będziemy potrzebować wszystkie trzy tryby jednocześnie, należy uruchomić trzy procesy AppServera. W serwerze PAS wystarczy jeden proces, a sposób pracy określa jedynie kod programu klienckiego.

Przypomnijmy, że dla serwera PAS połączenie z wybraną sesją ABL może być warunkowo związane/zwolnione poprzez ustawienie atrybutu SESSION:SERVER-CONNECTION-BOUND-REQUEST.

Zdefiniujmy teraz dwie procedury:
bound.p

/* bound.p  */
SESSION:SERVER-CONNECTION-BOUND-REQUEST = TRUE.

oraz unbound.p

/* unbound.p  */
SESSION:SERVER-CONNECTION-BOUND-REQUEST = FALSE.
QUIT.

Pierwsza procedura (patrz poprzedni artykuł) wiąże sesję klienta ABL z sesją PAS, drugą ją zwalnia.
Przejdżmy teraz do ustawień serwera PAS w OE Explorer -> Startup Parameters and Environment. Możemy podać tu nazwy procedur dla różnych operacji. Podobne ustawienia są możliwe na klasycznych AppSerwerze.

Dla trybu state-reset wystarczy podać nazwy tych procedur dla Connect i Disconnect jak na powyższym rysunku. Komenda QUIT powoduje zresetowanie sesji ABL. Jeśli procedury te zawierają inne Wasze instrukcje, to instrukcja wiążąca powinna być na początku w bound.p a procedura zwalniająca i QUIT na końcu unbound.p.

Dla trybu state-aware postępujemy analogicznie, tylko że w procedurze unbound.p NIE umieszczamy instrukcji QUIT.

Dla trybu stateless nie trzeba robić żadnych zmian ponieważ wiązanie i zwalnianie sesji może wystąpić w różnych miejscach logiki biznesowej i zależy od dewelopera aplikacji.

Load Balancing. Jeśli posiadamy produkt Name Server Load Balancer, to może konfigurować balansowanie obciążeniem klasycznego AppSerwera. Polega to z grubsza na dodaniu logicznej nazwy serwisu w kilku instancjach AppSerwera i określeniu procentu obciążenia w polu Weight.

Serwer aplikacji PAS nie łączy się jednak przez Name Server i nie da się tego tutaj tak zrobić. Load balancing dla PASOE można skonfigurować korzystając ze standardów HTTP takich jak: Apache proxy host load balancing, Tomcat load balancing, Amazon load balancing. Zainteresowanych tym tematem odsyłam do dokumentacji: Progress® Application Server for OpenEdge®: Administration Guide.

Procedury ABL i serwer aplikacji PAS cz.I

Od kilku miesięcy informujemy o nowych możliwościach serwera aplikacji PAS. Pytanie co z uruchamianiem procedur ABL, które działały do tej pory na klasycznym AppSerwerze? Czy trzeba je modyfikować? Czy migracja jest trudna dla przeciętnego programisty? Na te i podobne pytania postaram się odpowiedzieć w tym i następnym artykule.

Zacznijmy od uruchomienia instancji serwera aplikacji PAS o nazwie oepas1 (podobnie jak w poprzednich artykułach).
Przypominam, że PAS jest podłączony do bazy sportsdb, a w PROPATH znajduje się podkatalog openedge, do którego skopiujemy procedury abl (patrz poniżej).

Do uruchamiania procedur openedge potrzebny jest aktywny adapter, obsługujący warstwę transportową apsv. Sprawdzamy czy jest on aktywny w OpenEdge Explorerze lub wpisując w przeglądarce url: localhost:[port]/apsv. Jak widać poniżej adapter jest aktywny.


Teraz możemy uruchomić na kliencie program podłączający się do serwera aplikacji i uruchamiający tę samą procedurę FindCustomerByName, którą uruchamialiśmy dla web serwisów SOAP.
Najpierw zrobimy to dla modelu SESSION-FREE.

/* SESSION-FREE  */

DEFINE VARIABLE hServer AS HANDLE NO-UNDO.
DEFINE VARIABLE lReturn AS LOGICAL NO-UNDO.
DEFINE VARIABLE CustomerNumber AS CHARACTER NO-UNDO.

CREATE SERVER hServer.
lReturn = hServer:CONNECT("-URL http://localhost:8810/apsv
  -sessionModel Session-free"). 

IF NOT lReturn THEN DO:
  DELETE OBJECT hServer NO-ERROR.
  RETURN ERROR "Failed to connect to the ABL web application: " + RETURN-VALUE.
END.

RUN FindCustomerByName ON hServer (INPUT "Lift Tours", INPUT-OUTPUT CustomerNumber).

MESSAGE CustomerNumber
    VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.

lReturn = hServer:DISCONNECT().
DELETE OBJECT hServer NO-ERROR. 

Kod podłączenia i uruchomienia procedury jest bardzo podobny jak dla klasycznego AppServera.

W OpenEdge Explorerze w widoku Sessions widać otwartą sesję ze stanem Unbound (Bound=false), czyli sesja agenta nie jest powiązana z klientem.


Kod dla podłączenia w modelu SESSION-MANAGED różni się tylko dla metody CONNECT. Jego działanie jest identyczne jak w powyższym przykładzie.

/* SESSION-MANAGED */

DEFINE VARIABLE hServer AS HANDLE  NO-UNDO.
DEFINE VARIABLE lReturn AS LOGICAL NO-UNDO.
DEFINE VARIABLE CustomerNumber AS CHARACTER NO-UNDO.

CREATE SERVER hServer.
lReturn = hServer:CONNECT
  ("-URL http://localhost:8810/apsv","","").

IF NOT lReturn THEN DO:
  DELETE OBJECT hServer NO-ERROR.
  RETURN ERROR "Failed to connect to the ABL web application: " + RETURN-VALUE.
END.

RUN FindCustomerByName ON hServer (INPUT "Lift Tours", INPUT-OUTPUT CustomerNumber).

MESSAGE CustomerNumber
    VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.

lReturn = hServer:DISCONNECT().
DELETE OBJECT hServer NO-ERROR. 

Ponieważ uruchomiłem 3 takie same programy na kliencie, to w widoku Agents mam co prawda dalej tylko jednego agenta, ale 3 jego sesje.


Żeby powiązać daną sesję agenta z klientem stosujemy tę samą sztuczkę jak w przypadku klasycznego AppSerwera: w procedurze uruchamianej na serwerze aplikacji dodajemy komendę:

SESSION:SERVER-CONNECTION-BOUND-REQUEST = TRUE.

Powiązanie stosuje się aby łatwiej zarządzać kontekstem, gdyż ta sama sesja agenta może obsłużyć kilka/wiele żądań pomiędzy aplikacją kliencką a daną sesją. Zwolnienie sesji odbywa się zazwyczaj przez uruchomienie tej samej komendy z wartością FALSE.  Poniżej, w widoku Sessions widać 3 sesje ze stanem Bound (Bound=true).


Ciąg dalszy niebawem w drugiej cześci…

Web Serwisy SOAP i serwer aplikacji PAS cz.II

W poprzedniej części wygenerowaliśmy 2 pliki r-kodów, które chcemy wystawić jako web serwisy SOAP. Mamy także wygenerowane przy pomocy narzędzia Proxy Generator pliki CustOrder.wsm oraz CustOrder.wsdl.

Plik .wsdl posłuży nam do wygenerowania dokumentacji o zdefiniowanym web serwisie. W oknie komend wpisujemy polecenie:

bprowsdldoc custorder.wsdl ./output

W podkatalogu output znajdziemy nowe pliki .html, zawierające informacje o zdefiniowanych procedurach, funkcjach itp. Są także przykłady kodu do przyłączenia i uruchomienia wybranej metody. Poniżej znajdują się definicje dwóch procedur z listą parametrów. Proszę zauważyć, że został automatycznie dodany parametr result.

Aby wdrożyć nowy web serwis musimy mieć instancję serwera PASOE. Posłużę sie tutaj tą samą instancją z przyłączoną bazą sports2000, jak opisałem w artykule o serwisach REST.

Kopiuję 2 r-kody do podkatalogu openedge w ścieżce PROPATH dla serwera aplikacji, a następnie uruchamiam komendę:

C:\WrkOpenEdge117\oepas1\bin\deploySOAP.bat custorder.wsm ROOT


Web serwis jest już wdrożony. Restartuję serwer aplikacji i przystępuję do testów. Najpierw FindCustomerByName.

DEFINE VARIABLE NAME AS CHARACTER NO-UNDO.
DEFINE VARIABLE CustomerNumber AS INTEGER NO-UNDO.
DEFINE VARIABLE result AS CHARACTER NO-UNDO.
    
DEFINE VARIABLE hWebService AS HANDLE NO-UNDO.
//DEFINE VARIABLE CustomerOrderObj AS HANDLE NO-UNDO.
DEFINE VARIABLE hPortType AS HANDLE NO-UNDO.

DEFINE VARIABLE lReturn AS LOGICAL NO-UNDO.

CREATE SERVER hWebService.

lReturn = hWebService:CONNECT("-WSDL 'http://192.168.1.21:8810/soap/wsdl?targetURI=CustOrder' 
                               -Port CustOrderObj").

RUN CustOrderObj SET hPortType ON SERVER hWebService.

RUN FindCustomerByName IN hPortType(INPUT "Lift Tours", INPUT-OUTPUT CustomerNumber, OUTPUT result).

MESSAGE CustomerNumber
    VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.

W wyniku dostajemy nr klienta dla zadanej nazwy.

Test dla GetOrderDetails wygląda podobnie.

DEFINE TEMP-TABLE OrderDetails
    FIELD OrderNum LIKE Order.OrderNum
    FIELD SalesRep LIKE Order.SalesRep
    FIELD OrderDate LIKE Order.OrderDate
    FIELD ShipDate LIKE Order.ShipDate
    FIELD TotalDollars AS DECIMAL
    FIELD OrderStatus LIKE Order.OrderStatus.
    
DEFINE VARIABLE hWebService AS HANDLE NO-UNDO.
DEFINE VARIABLE CustomerOrderObj AS HANDLE NO-UNDO.
DEFINE VARIABLE hPortType AS HANDLE NO-UNDO.
DEFINE VARIABLE result AS CHARACTER NO-UNDO.
DEFINE VARIABLE lReturn AS LOGICAL     NO-UNDO.

CREATE SERVER hWebService.

lReturn = hWebService:CONNECT("-WSDL 'http://192.168.1.21:8810/soap/wsdl?targetURI=CustOrder' 
                               -Port CustOrderObj").

RUN CustOrderObj SET hPortType ON SERVER hWebService.

RUN GetOrderDetails IN hPortType(INPUT 3, OUTPUT RESULT, OUTPUT TABLE OrderDetails).


FOR EACH OrderDetails:
   DISPLAY OrderDetails.
END.

I wynik…

Pragnę zwrócić tutaj uwagę na dwie sprawy. Po pierwsze dla serwisów SOAP nie trzeba było definiować specjalnego projektu w OE Dev Studio. Po drugie nie trzeba było instalować i konfigurować Java Servlet Engine, Web Servera i WS adaptera, bo PASOE wszystko to już zawiera. To ogromne zaleta i ułatwienie pracy.
Życzę udanych projektów!

Web Serwisy SOAP i serwer aplikacji PAS cz.I

Tak się złożyło, że ostatnio kilkakrotnie byłem pytany przez klientów o  wystawianie web serwisów typu SOAP. Takie serwisy określane są czasem jako te “stare” w stosunku do serwisów typu REST. Są większym obciążeniem dla sieci (format danych XML) ale od wielu lat umożliwiają integrację/wymianę danych między różnymi systemami. Wspierane są też przez tradycyjny OpenEdge AppServer.

Pytanie, na które teraz należy odpowiedzieć to, czy nowy serwer aplikacji PASOE, lub jak kto woli PAS (Progress Application Server for OpenEdge) obsługuje także serwisy SOAP.

Żeby odpowiedzieć na to pytanie przyjrzyjmy się najpierw właściwościom pliku dla lokalnej instancji serwera w katalogu roboczym …\oepas1\conf\openedge.properties. W pliku tym znajdziemy wpisy:

[oepas1.ROOT.SOAP],

[oepas1.ROOT.REST],

[oepas1.ROOT.WEB],

[oepas1.ROOT.APSV].

Są to cztery warstwy transportowe; dla każdej zainstalowany i skonfigurowany jest adapter. Żeby wiedzieć czy dany adapter jest włączony trzeba sprawdzić ustawienie adapterEnabled; jeśli 1 to włączony. Pamiętajmy, że te ustawienia lepiej zmieniać poleceniem z linii komend niż bezpośrednio w pliku, a więc np.:
oeprop oepas1.ROOT.SOAP.adapterEnabled=1

Innym miejscem gdzie możemy sprawdzić powyższe ustawienia jest OpenEdge Explorer. Należy otworzyć widok w instancji serwera dla aplikacji webowej ABL ROOT (ABL WebApp: ROOT).

OK, ale jak w ogóle przygotować kod aplikacji do wystawienia za pomocą serwisu SOAP? Pierwszy etap jest dokładnie taki sam jak dla klasycznego AppServera. Ponieważ nie wszyscy korzystali dotychczas z tej metody omówimy ją na prostym przykładzie.

Mamy 2 oddzielne procedury, które chcemy wyświetlić w postaci web serwisów. Pierwsza FindCustomerByName.p zwraca numer rekordu Customer dla zadanej wartości Name.

DEFINE INPUT PARAMETER NAME AS CHARACTER.
DEFINE INPUT-OUTPUT PARAMETER CustomerNumber AS INTEGER.

FIND FIRST Customer WHERE Customer.NAME = NAME NO-ERROR.
IF AVAILABLE Customer THEN
    CustomerNumber = Customer.CustNum.
ELSE
    CustomerNumber = ?.

Druga procedura GetOrderDetails.p zwraca tablicę tymczasową zawierającą dane zamówień dla wybranego rekordu Customer.

DEFINE INPUT PARAMETER thisCust AS INTEGER NO-UNDO.

DEFINE TEMP-TABLE OrderDetails
    FIELD OrderNum LIKE Order.OrderNum
    FIELD SalesRep LIKE Order.SalesRep
    FIELD OrderDate LIKE Order.OrderDate
    FIELD ShipDate LIKE Order.ShipDate
    FIELD TotalDollars AS DECIMAL
    FIELD OrderStatus LIKE Order.OrderStatus.
    
DEFINE OUTPUT PARAMETER TABLE FOR OrderDetails.

FUNCTION GetTotalDollars RETURNS DECIMAL PRIVATE
  (OrderNum AS INTEGER)  FORWARD.

FIND FIRST Customer WHERE Customer.CustNum = thisCust NO-ERROR.
IF NOT AVAILABLE Customer THEN RETURN ERROR. 

EMPTY TEMP-TABLE OrderDetails.

FOR EACH Order WHERE Order.CustNum = thisCust:
    CREATE OrderDetails.
        ASSIGN 
            OrderDetails.OrderNum = Order.OrderNum
            OrderDetails.SalesRep = Order.SalesRep
            OrderDetails.OrderDate = Order.OrderDate
            OrderDetails.ShipDate = Order.ShipDate
            OrderDetails.OrderStatus = Order.OrderStatus.

        OrderDetails.TotalDollars = GetTotalDollars(Order.OrderNum).
END.

FUNCTION GetTotalDollars RETURNS DECIMAL PRIVATE
  (OrderNum AS INTEGER) :

    DEFINE VARIABLE TotalDollars AS DECIMAL NO-UNDO INITIAL 0.0.

    FOR EACH OrderLine WHERE OrderLine.OrderNum = OrderNum:
        TotalDollars = TotalDollars + OrderLine.ExtendedPrice.
    END.
    RETURN TotalDollars.
END FUNCTION.

Obie procedury kompilujemy do postaci r-kodu.
Teraz otwieramy narzędzie Proxy Generator. Służy ono do generowania plików tzw. proxy do integracji z interfejsem Javy i .NET, a także do definiowania web serwisów SOAP.
Definiowanie serwisów jest bardzo proste. Tworzymy nowy projekt np. CustOrder i podajemy ścieżkę do r-kodów.

Następnie w menu Procedure -> Add dodajemy 2 r-kody jako procedury Non-persistent.

Teraz przechodzimy do ustawień: Options -> Preferences. Ustawiamy Client type: Web Service oraz Output directory gdzie zostaną zapisane wygenerowane pliki. Pozostałe ustawienia są wykorzystywane dla klasycznego AppServera i zostaną zignorowane przez serwer PASOE. Przypominam, że dla nowego serwera aplikacji nie trzeba instalować i konfigurować JSE, adaptera WSA ponieważ te elementy są już wbudowane w produkt.

Teraz wybieramy opcję Generate i OK. W wybranym katalogu pojawiły się pliki CustOrder.wsm oraz CustOrder.wsdl (oraz log).
O tym jak wykorzystać te pliki i jak wdrożyć web serwis w serwerze aplikacji PASOE napiszę następnym razem.

Wystawianie serwisów REST z aplikacji ABL cz.II

Po dłuższej przerwie wracamy do zagadnienia związanego z udostępnieniem serwisu typu REST, który został zdefiniowany tutaj.

Taki zdefiniowany serwis trzeba wyeksportować. W tym celu klikamy prawym przyciskiem myszy na nazwę serwisu i wybieramy Export. Eksportowanie można zrobić na 2 sposoby: jako plik WAR lub ZIP. W pierwszym przypadku jest to oddzielny kompletny serwis, w drugim, serwis będzie doinstalowany do domyślnego serwisu ROOT  (Export services incrementally). Tutaj wybieram tę drugą metodę.


Proces wdrożenia przeprowadzimy na innej maszynie, na bazie danych sportsdb (kopia bazy sports2000).

Do wdrożenia potrzebny będzie plik klasy (customer.r) oraz oczywiście definicji serwisu (CustomerServ.zip).

Na nowej maszynie mamy instalację OpenEdge 11.7 z katalogiem roboczym C:\WrkOpenEdge117 i domyślną instalacją PAS: oepas1.

Najpierw w katalogu C:\WrkOpenEdge117\oepas1\openedge umieszczamy plik customer.r. Katalog ten znajduje się na liście PROPATH serwera aplikacji. W parametrach startowych agenta podajemy nazwę bazy, tutaj: -db sportsdb -ld sports2000. Parametr -ld (logiczna nazwa bazy) jest konieczny, ponieważ plik customer.r był skompilowany dla bazy sports2000. Zarówno PROPATH jak i parametry przyłączenia bazy można zobaczyć na poniższym obrazie.

Teraz nawigujemy do katalogu C:\WrkOpenEdge117\oepas1\webapps
gdzie umieszczamy plik CustomerServ.zip.

Uruchamiamy proenv i upewniamy się, że jesteśmy w powyższym katalogu. Podajemy komendę, która wdroży serwis przyrostowy dla ROOTa:

C:\WrkOpenEdge117\oepas1\bin\deployREST.bat CustomerServ.zip ROOT

Po zrestartowaniu serwera aplikacji serwis REST jest gotowy. Możemy go teraz przetestować. W tym celu w przeglądarce Chrome otwieramy Advanced REST client. Aplikacja jest typu application/json. Podajmy żądanie GET dla pobrania wybranego rekordu (metoda ReadCustomer): localhost:8810/rest/CustomerServService/Customers/5

Odpowiedź widać poniżej: rekord o nr 5 został pobrany i wyświetlony.

Podobnie sprawdzamy działanie metody ReadAllCustomers.

Wyświetlone są wszystkie rekordy.

Serwis działa poprawnie.

Następnym razem omówię trochę zapomniane web serwisy typu SOAP, ale wywoływane przez nowy serwer aplikacji PASOE.

1 2 3