Geschäftslogik in die Datenbank – Umstellung eines

Werbung
Geschäftslogik in die Datenbank
– Umstellung eines Kernbanksystems –
Michael Meyer
Berenberg Bank
Hamburg
Schlüsselworte:
PL/SQL, Objektorientierung, Oracle Types, XML, Zentralisierung der Geschäftslogik, komponentenbasierte Programmierung
Einleitung
Im Rahmen eines mehrjährigen Projektes wird ein komplettes Kernbankapplikation von einer
cobolbasierten Lösung auf PL/SQL ungestellt. Die gesamte Geschäftslogik wird unmittelbar in der
Datenbank abgebildet. Es entstanden/entstehen viele zentrale Komponenten, die einmal entwickelt
werden und in vielen Sachgebieten Anwendung finden. Hierbei kommen diverse Oracle Techniken zu
Einsatz (Oracle Types, Streams, XML). Gezeigt werden Beispiele, wie diese Techniken in der Praxis
angewendet werden, wo sie sinnvoll nutzbar sind und wo nicht.
Nach einer Vorstellung der Grundideen des Systems, wird die Umsetzung an Best Practice-Fällen
erläutert.
Abschließend wird kurz auf die Problematik der Bereitstellung von Entwicklungs- und
Testdatenbanken bzgl. Datenanonymisierung eingegangen.
Geschäftslogik in der Datenbank
Häufig werden Datenbanken als reiner Ablage- oder Speicherort benutzt. Datenbanken stellen dem
Entwickler heutzutage aber deutlich mehr Funktionalität zur Verfügung. In SQL/PLSQL können komplexe Logiken direkt in der Datenbank programmiert werden. Daten müssen damit nicht von einem
anderen Layer (Tier) geladen, dort verarbeitet und dann wieder zurück geschrieben werden. Alles
kann in unmittelbarer „Nähe“ der Daten stattfinden.
Weiterhin bietet die Datenbank zahlreiche Packages, die Basisfunktionalitäten bereitstellen (Mailversand, Dateihandling, ...).
-- define constructors
, CONSTRUCTOR FUNCTION ROW_DH_MAKLER RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_DH_MAKLER(DH_MAKLER_ID NUMBER,
SCHLUESSEL VARCHAR2, BESCHREIBUNG VARCHAR2,
ERSTELLT_ID NUMBER, ERSTELLT_ZST DATE, GEAENDERT_ID NUMBER,
GEAENDERT_ZST DATE, GEAENDERT_ZAEHLER NUMBER, GELOESCHT_ZST DATE)
RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_DH_MAKLER(IN_DH_MAKLER_ID NUMBER)
RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_DH_MAKLER(IN_SCHLUESSEL VARCHAR2,
IN_GELOESCHT_ZST DATE) RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_DH_MAKLER(IN_SCHLUESSEL VARCHAR2)
RETURN SELF AS RESULT
-- define member functions
, MEMBER FUNCTION ROW_EXISTS(IN_DH_MAKLER_ID NUMBER) RETURN BOOLEAN
, MEMBER FUNCTION ROW_EXISTS(IN_SCHLUESSEL VARCHAR2,
IN_GELOESCHT_ZST DATE) RETURN BOOLEAN
, MEMBER FUNCTION ROW_EXISTS(IN_SCHLUESSEL VARCHAR2) RETURN BOOLEAN
, OVERRIDING MEMBER FUNCTION compare(in_type1 GLOBAL.TYPE_OBJECT,in_type2
GLOBAL.TYPE_OBJECT) RETURN INTEGER
-- define member procedures
, MEMBER PROCEDURE ROW_INSERT
, MEMBER PROCEDURE ROW_UPDATE
, MEMBER PROCEDURE ROW_MERGE
, MEMBER PROCEDURE ROW_SAVE
, MEMBER PROCEDURE ROW_DELETE
, MEMBER PROCEDURE ROW_SELECT(IN_DH_MAKLER_ID NUMBER)
, MEMBER PROCEDURE ROW_SELECT(IN_SCHLUESSEL VARCHAR2,
IN_GELOESCHT_ZST DATE)
, MEMBER PROCEDURE ROW_SELECT(IN_SCHLUESSEL VARCHAR2)
, MEMBER PROCEDURE ROW_DEFAULT
, MEMBER PROCEDURE ROW_LOCK
, MEMBER PROCEDURE ROW_LOCK(IN_DH_MAKLER_ID NUMBER)
, MEMBER PROCEDURE ROW_LOCK(IN_SCHLUESSEL VARCHAR2,
IN_GELOESCHT_ZST DATE)
, MEMBER PROCEDURE ROW_LOCK(IN_SCHLUESSEL VARCHAR2)
) NOT FINAL
Der ROW-Type wird bereits von einem allgemeinen Type (GLOBAL.TYPE_OBJECT) abgeleitet, der
Standardroutinen z. B. zur Ausgabe oder zum Vergleich eines Objektes definiert. Direkte DMLBefehle im PLSQL-Programmcode werden mit der Verfügbarkeit solcher ROW-Types in den meisten
Fällen überflüssig. Die Änderung eines Attributes wird mit folgendem Code erledigt:
declare
r_dh bbs.row_dh_makler;
begin
r_dh
:= bbs.row_dh_makler(1);
r_dh.beschreibung := 'geänderte Beschreibung';
r_dh.row_save();
end;
Businesstypes nutzen typischerweise diverse ROW-Types und andere Businesstypes und stellen
darüber komplexe Geschäftslogik bereit. So verbindet der TYPE_KUNDE alle Depots und Konten
eines Kunden:
CREATE OR REPLACE TYPE TYPE_KUNDE UNDER BBS.ROW_KUNDE
(
-- %Version
$Revision: 54699 $
-- attributes
DEPOT
BBS.TABLE_DEPOT,
KONTEN_BBS
BBS.TABLE_KONTO_BBS,
-- ...
-- ...
-- ...
-- define constructors
, CONSTRUCTOR FUNCTION TYPE_KUNDERETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION TYPE_KUNDE(IN_STAMMNR VARCHAR2)
RETURN SELF AS RESULT
-- define member procedures
, MEMBER FUNCTION is_bank
RETURN BOOLEAN
-- ...
) NOT FINAL
Der hier enthalte Type BBS.TABLE_KONTO_BBS enthält wiederum Types für die Darstellung der
Konditionen und Gebühren:
CREATE OR REPLACE TYPE TYPE_KONTO_BBS UNDER BBS.ROW_KONTO_BBS
(
-- %Version
$Revision: 54699 $
t_konditionen
BBS.TYPE_KONTO_BBS_KONDITIONEN,
tab_gebuehrbetrag
BBS.TABLE_GEBUEHRBETRAG,
-- Constructor
CONSTRUCTOR FUNCTION TYPE_KONTO_BBS(IN_KONTO_BBS_ID IN NUMBER)
RETURN SELF AS RESULT,
CONSTRUCTOR FUNCTION type_konto_bbs(in_kontonummer IN VARCHAR2)
RETURN SELF AS RESULT,
-- ...
-- ...
)
NOT FINAL
Wird z. B. die Rechtsform eines Kunden geändert, erfolgt dies im Prinzip über den folgenden Code:
declare
t_kunde bbs.type_kunde;
begin
t_kunde := bbs.type_kunde('1234567890');
t_kunde.rechtsformid := 52;
t_kunde.row_save();
end;
Der bei Nutzung der Types entstehende Programmcode ist u. E. leichter les- und wartbar als
„traditionelle“ DML-Anweisungen. Geeignet ist diese Vorgehen für eher einzelsatzorientierte
Programme – Massendatenupdates sollten über normale DML-Anweisungen erfolgen. Nachteilig bei
dem Verfahren ist, dass bei UPDATEs immer alle Spalten einer Zeile aktualisiert werden.
Data-Masking für Entwicklungsdatenbanken
Für Entwicklungs- und Testzwecke müssen realistische Datenkonstellationen und -mengen zur
Verfügung stehen, so dass man schnell auf die Idee kommt, dafür Kopien der Produktionsdatenbank
zu nutzen. Diese enthalten aber schützenswerte Daten (Kundennamen, Adressen, Umsatzdaten), die
für einen Entwickler nicht zugänglich sein dürfen.
Das Oracle-Data-Masking-Pack (Option!) erzeugt PLSQL-Code für eine solche Anonymisierung
(Masking genannt). Dafür werden die sensitiven Tabellenspalten identifiziert und deren Inhalte dann
während eines Maskierungslaufes anonymisiert. Es stehen diverse Anonymisierungsalgorithmen bereit
(Zufallswerte, Shuffle, Substring, User Defined Function, ...). Die referenzielle Integrität der Daten
bleibt erhalten.
Nach anfänglichen Problemen läuft das Data-Masking bei uns inzwischen recht problemlos. Die
Laufzeit für etwa 60 Tabellen mit ca. 250 anonymisierten Spalten beträgt etwa 2 Stunden.
Sichtbarkeiten mit Virtual Private Database (Column-Level-Security)
Teilweise ist es erforderlich, dass Entwickler lesenden Zugriff auf Produktionsdatenbanken erhalten,
um Fehlersituationen analysieren zu können. Hier kann das „Column-Level-Security“-Feature der
VPD helfen. Dabei werden alle Zeilen einer Tabelle gezeigt, aber es werden statt der „echten“ Inhalte
der zu schützenden Spalten NULL-Werte angezeigt. In aller Regel sind die schützenswerten Spalten
einer Tabelle für die Analyse von Fehlersituationen nicht wichtig.
Zur Aktivierung der Column-Level-Security sind zunächst wiederum die sensitiven Tabellenspalten zu
identifizieren und für diese dann eine Policy anzulegen:
begin
DBMS_RLS.ADD_POLICY (object_schema=>'BBS', object_name=>'KUNDE',
policy_name=>'BBS_KUNDE',
function_schema=>'BV',
policy_function=>'pa_vpd_reader.fu_policy',
sec_relevant_cols=>'VORNAME,NACHNAME',
sec_relevant_cols_opt=>dbms_rls.ALL_ROWS);
end;
/
Entfernen einer Policy:
begin
DBMS_RLS.DROP_POLICY ('BBS', 'KUNDE', 'BBS_KUNDE');
end;
/
In der Policyfunktion selbst wird für den festen Anmeldeuser (bei uns „READER“), der die
Problemanalyse durchführt, ein Wert, der zu FALSE evaluiert wird zurückgegeben. Alle anderen User
erhalten hier ein TRUE, damit sie die Spalteninhalte weiterhin sehen können.
FUNCTION
VARCHAR2
IS
fu_policy(
obj_schema
VARCHAR2,
obj_name
VARCHAR2
)
RETURN
BEGIN
v_retVal := '0=0';
IF USER = 'READER' THEN
v_retVal := '1=0';
END IF;
RETURN v_retVal;
END;
Ein:
SELECT VORNAME, NACHNAME FROM KUNDE;
liefert dann für den READER-User:
Der Einsatz dieser Technik für etwa 60 Tabellen mit ca. 250 relevanten Spalten zeigt keine spürbaren
Performancebeeinträchtigungen
Herunterladen