Donnerstag, 6. August 2015

REST-Services: Viele Datenbanken - eine ORDS-Instanz. So geht's.

In diesem Blog-Posting geht es um ein Setup-Thema für die Oracle Rest Data Services (ORDS). Im ersten Blog-Posting ('Auto-REST' für Tabellen) habt Ihr erfahren, wie man ORDS herunterlädt und mit wenigen Handgriffen installiert - dabei wird auch die Verbindung zur Datenbank eingerichtet. Standardmäßig ergibt sich also pro Datenbank eine ORDS-Instanz. Bei mehreren Datenbanken also mehrere ORDS-Instanzen. Möchte man aber über einen HTTP-Endpoint auf alle REST-Services zugreifen, so ergeben sich zwei Möglichkeiten:

  • Man setzt einen Webserver (bspw. Apache) als HTTP-Endpoint auf - Weiterleitungsregeln sorgen dafür, dass bestimmte URL-Schemata auf bestimmte ORDS-Instanzen abgebildet werden.
  • Man setzt nur eine ORDS-Instanz auf und konfiguriert diese für mehrere Datenbanken.

Zentralen Webserver als Endpoint für ORDS-Instanzen einrichten

Wie die erste Variante genau konfiguriert wird, hängt von vielen Details ab. So ist die Frage bedeutsam, ob ORDS im Standalone-Modus läuft oder in einem Java-Server wie Weblogic, Glassfish oder Tomcat deployed wird. Hier ist nur der Standalone-Modus beschrieben, für das Deployment von ORDS in einem Java-Container wird es eigene Blog-Postings geben. Angenommen, es liegen zwei ORDS-Instanzen auf dbserver1 und dbserver2 vor - beide sind bereits mitsamt Datenbankverbindung konfiguriert und jeder - für sich - funktioniert. Die folgenden Schritte müssen natürlich für beide Instanzen gemacht werden.

Navigiert nun zu den Konfigurationsdateien eurer ORDS-Instanzen - und dort zur Datei standalone.properties.

{ORDS-Config-Directory}
`-- ords
    |-- conf
    |   |-- apex_pu.xml
    |-- defaults.xml
    `-- standalone
        `-- standalone.properties

Ändert die Property standalone.context.path in der ersten ORDS-Instanz von /ords auf /restdb1 und in der zweiten Instanz auf /restdb2. Für die erste Instanz sieht das dann so aus.

#Thu Aug 06 10:30:54 CEST 2015
jetty.port=8081
standalone.context.path=/restdb1
standalone.doc.root=/home/oracle/ords/config/restdb1/standalone/doc_root
standalone.static.context.path=/i
standalone.static.do.not.prompt=true

Benennt dann das Verzeichnis {ORDS-Config-Directory}/ords nach {ORDS-Config-Directory}/restdb1 (zweite ORDS-Instanz analog) um. Schließlich benennt Ihr die Java-Datei ords.war (aus welcher der ORDS heraus gestartet wird) nach restdb1.war (zweite ORDS-Instanz analog) um. Die Verzeichnisstruktur sollte dann für die ORDS-Instanz auf dem dbserver1 so aussehen.

.
|-- {ORDS-Config-Directory}
|   `-- restdb1
|       |-- conf
|       |   `-- apex_pu.xml
|       |-- defaults.xml
|       `-- standalone
|           `-- standalone.properties
|-- derby.log

:

|-- readme.html
`-- restdb1.war

Nun könnt Ihr die ORDS-Instanzen starten - nun muss aber das WAR-File restdb1.war bzw. restdb2.war mit "java -jar restdb1.war" gestartet werden. Anstelle des URL-Pfads /ords/ erreicht Ihr eure REST-Services nun unter /restdb1/ bzw. /restdb2/.

http://dbserver1.mydomain.de:8080/restdb1/{...}
http://dbserver2.mydomain.de:8080/restdb2/{...}

Nun geht es an die Konfiguration des Apache Webserver - auch hier gehen wir davon aus, dass dieser bereits installiert und konfiguriert ist. Die Direktiven für das mod_proxy könnten in etwa wie folgt aussehen.

ProxyPreserveHost On

ProxyPass        /restdb1/ http://dbserver1.mydomain.de:8080/restdb1/
ProxyPassReverse /restdb1/ http://dbserver1.mydomain.de:8080/restdb1/

ProxyPass        /restdb2/ http://dbserver2.mydomain.de:8080/restdb2/
ProxyPassReverse /restdb2/ http://dbserver2.mydomain.de:8080/restdb2/

Von nun an werden alle REST-Requests mit dem URL-Pfad /restdb1/ am die ORDS-Instanz auf dem dbserver1 geleitet, die mit dem Pfad /restdb2 folgerichtig an die andere ORDS-Instanz auf dbserver2. Alle REST-Requests werden nur noch an den Apache-Webserver gerichtet, die konkreten ORDS-Instanzen sind nicht mehr sichtbar; der Client hat den Eindruck, als gäbe es nur einen einzigen ORDS-Server.

Der beschriebene Ansatz ist gut machbar, solange man pro Server nur eine Datenbank hat - der Apache-Webserver dient dann sehr schön als zentraler Einstiegspunkt.

ORDS-Instanz für mehrere Datenbanken einrichten

Hat man dagegen mehrere Datenbankinstanzen auf einem Server, so bräuchte man, passend dazu, genauso viele ORDS-Instanzen. Jede ORDS-Instanz ist ein eigener Java-Prozess - optimale Ressourcennutzung sieht anders aus.

Eine andere Variante ist daher es, von vorneherein nur eine ORDS-Instanz aufzusetzen und dann darin zwei oder mehrere Datenbanken zu konfigurieren - wie das geht, ist im folgenden beschrieben. Ausgangspunkt ist eine vorhandene ORDS-Instanz auf dbserver1, die bereits für eine Datenbank konfiguriert ist (siehe erstes Blog-Posting ('Auto-REST' für Tabellen).

Stoppt zunächst diese ORDS-Instanz und konfiguriert die zweite Datenbank danach wie folgt:

$ java -jar ords.war setup --database dbserver2

Das Kommando setup --database richtet eine neue Datenbankverbindung ein - Ihr müsst auf jeden Fall den Namen des Connection Pool an den Aufruf anhängen (hier: dbserver2). Dann werdet Ihr nochmals durch den Dialog zum Setup der Datenbankverbindung geführt.

Enter the name of the database server [dbserver1.mydomain.de]:dbserver2.mydomain.de
Enter the database listen port [1521]:1521
Enter the database service name [orcl]:db-servicename.db.domain
Enter 1 if you want to verify/install Oracle REST Data Services schema or 2 to skip this step [1]:1
Enter the database password for ORDS_PUBLIC_USER:******
Confirm password:******
Enter 1 if you want to use PL/SQL Gateway or 2 to skip this step [1]:2
Enter 1 to specify passwords for Application Express RESTful Services database users (APEX_LISTENER, APEX_REST_PUBLIC_USER) or 2 to skip this step [1]:2
Aug 06, 2015 11:40:07 AM oracle.dbtools.common.config.file.ConfigurationFilesBase update
INFO: Updated configurations: dbserver2_pu
Aug 06, 2015 11:40:07 AM oracle.dbtools.rt.config.setup.SchemaSetup install
INFO: Oracle REST Data Services schema version 3.0.1.177.18.02

Nun ist der Connection Pool für eure zweite Datenbank eingerichtet. Dennoch solltet Ihr die XML-Konfigurationsdateien nochmals prüfen. In der Datei ${ORDS_CONFIG_DIR}/ords/defaults.xml speichert ORDS die Einstellungen, die für alle Datenbankverbindungen gültig sind - im Verzeichnis ${ORDS_CONFIG_DIR}/ords/conf befindet sich sich darüber hinaus eine XML-Datei für jede eingerichtete Datenbank - sie hält die Einstellungen, die nur für diese Datenbankverbindung gültig sind.

{ORDS-Config-Directory}
|-- ords
|   |-- conf
|   |   |-- apex_pu.xml
|   |   `-- dbserver2_pu.xml
|   |-- defaults.xml
|   `-- standalone
|       `-- standalone.properties

Die Datei apex_pu.xml enthält die Konfiguration der Datenbank, die bei der ersten Installation der ORDS-Instanz eingerichtet wurde - dieser Connection-Pool trägt immer den Namen apex. Die Namen weiterer Dateien richten sich nach dem Namen, der bei Einrichtung für den Connection-Pool verwendet wurde. Schaut nun in die neue Datei (dbserver2_pu.xml) hinein. Der Inhalt sollte wie folgt aussehen.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Saved on Thu Aug 06 11:40:06 CEST 2015</comment>
<entry key="db.hostname">dbserver2.mydomain.de</entry>
<entry key="db.servicename">db-servicename.db.domain</entry>
<entry key="db.username">ORDS_PUBLIC_USER</entry>
<entry key="db.password">@052804911E20317F33FA63EBF21DFC1C76</entry>
</properties>

Wenn etwas fehlt oder nicht richtig eingestellt ist, könnt Ihr es hier korrigieren. Das Passwort für die Datenbankverbindung ist verschlüsselt gespeichert. Wenn Ihr es ändern möchtet, tragt es im Klartext ein, angeführt von einem Ausrufezeichen ("!oracle"). Beim nächsten Start wird ORDS es dann verschlüsseln.

Wenn Ihr den ORDS dann startet, werdet Ihr zunächst feststellen, dass Ihr nun zwar zwei Datenbank-Connection-Pools habt, aber trotzdem keinen einzigen REST-Service mehr erreichen könnt - er wird immer mit einer HTTP-404 Fehlermeldung (Not Found) antworten. Grund ist, dass das automatische Mapping der URL, auf einen REST-Service in einem Datenbankschema, bei mehreren Datenbankverbindungen nicht mehr verfügbar ist. Solange nur eine Datenbankverbindung vorhanden war, konnte eine URL /ords/scott/rest-emp automatisch auf das REST-Modul rest-emp im Datenbankschema SCOTT abgebildet werden - bei zwei oder mehreren Datenbanken geht das nicht mehr.

Also müssen die URL-Mappings manuell gepflegt werden. Dazu dient das ORDS-Kommando map-url. Das Attribut --type legt fest, wie das URL-Matching erfolgen soll; base-path ist das einfachste und am häufigsten verwendete. Daneben steht auch noch das mächtigere regex bereit, mit dem auch komplexere URL-Mappings einrichten kann. Wichtig ist das Attribut --schema-name: Es legt fest, in welchem Datenbankschema die REST-Services zu finden sind. Die letzten beiden Parameter bezeichnen das URL-Pattern selbst und den Datenbank-Connection-Pool. Die folgenden beiden Aufrufe, die übrigens auch im laufenden Betrieb abgesetzt werden können, richten zwei URL-Mappings ein.

$ java -jar ords.war map-url --type base-path --schema-name scott /db1/scott apex
$ java -jar ords.war map-url --type base-path --schema-name scott /db2/scott dbserver2

Der URL-Präfix /ords/db1/scott wird demnach auf den Datenbankuser SCOTT und den Default-Connection-Pool apex abgebildet. Der Pfad /ords/db2/scott analog dazu auf das Schema SCOTT im Connection Pool dbserver2. Ansonsten funktionieren die REST-Services wie gehabt. Ein GET Request auf /ords/db2/scott/emp spricht also ..

  • ... im Connection-Pool dbserver2 ...
  • ... im Schema SCOTT ...
  • ... mit der Methode GET ...
  • ... das REST-Modul emp an.

Wenn Ihr nun in einer der Datenbanken mit ORDS.ENABLE_SCHEMA ein neues Schema für REST-Services freischaltet, könnt Ihr es nicht sofort mit REST-Requests ansprechen - zuerst muss mit java -jar ords.war map-url ein URL-Mapping für dieses Schema eingerichtet werden. Im Gegenzug hat man nur noch einen einzigen ORDS-Server, der aber REST-Services in vielen Datenbanken bereitstellt.

Probiert es aus - es braucht ein wenig Übung, um mit den verschiedenen Setup-Varianten vertraut zu werden; im Gegenzug bekommt man REST-Endpoints für eine oder viele Datenbanken - nach Maß! Viel Spaß damit.