Session D-POBJ

Persistente Objekte - Realisation
in VFP

Burkhard Stiller
Wizards & Builders GmbH


Motivation

Diese Session entstand aus der Notwendigkeit heraus, für das von mir projektierte Workflow Management System eine Technik zu entwickeln, mit deren Hilfe instanziierte Objekte im Hauptspeicher "persistent gemacht" werden können. Der Speicherungsmethodik die Verwendung findet, wird in der Session D-STOR als ObjectSTORE-Technology detailliert besprochen.

Abstrakt

Vorhaben, die Eigenschaften von Speicherobjekten in Tabellen ablegen zu können, kranken an der bekannten Tatsache, die landläufig als der Darstellungsbruch zwischen OO Programmierung und dem RDBM Konzept bezeichnet wird. Dabei wäre es in sehr vielen Anwendungsbereichen innerhalb der objektorientierten Programmierung äußerst vorteilhaft, wenn eine universelle, objektorientiert implementierte Technik verfügbare wäre, um damit die flexiblen Speicherstrukturen der instanziierten Programmobjekte zur Laufzeit auf ebenso flexiblen Tabellenstrukturen abzubilden, um diese neue Art von Objekten persistent zu machen.

Praxisbezug

Wenn man unter VFP ein prozessorientiertes Softwaredesign betreibt, kommt man irgendwann einmal an einen Punkt, an dem Prozesse länger als die Programmlaufzeit dauern. In der Praxis sind dies z.B. Vorgänge, die aus dem Workflow Management bekannt sind. Dokumentenbasierte Prozesse in Unternehmen, die ein Dokument durch verschiedene Abteilungen zur Weiterbearbeitung leiten, sind ein Beispiel dafür. Immer wenn Prozesse durch ein Programm abgearbeitet werden sollen, die in der Regel länger als die Arbeitssitzung am Programm selbst dauern, ist es wünschenswert, wenn man den Prozeß beenden kann, um ihn später an der Stelle des Abbruchs wieder aufzunehmen. Dies wird auch als ein Forward Recovery bezeichnet, also eine vorwärts gerichtete Wiederherstellung eines bestimmten Zustands. Sind in solchen Prozessen instanziierte Objekte eingebunden, so muß man eine Möglichkeit finden, diese genauso wieder "vorwärts gerichtet" zu instanziieren, damit der letzte aktuelle Status der Objekte nach dem Wiederaufsetzen der Applikation hergestellt werden kann.

Verwendung findet diese Technik in allen unternehmensweiten Workflow Management Systemen, die Prozesse über längere Zeitspannen definieren und abarbeiten können.

Implementation

Während all der Jahre, in denen ich mit FoxPro und später mit Visual FoxPro entwickelt habe, gefiel mir am meisten die konsequente Nutzung der DBF-Tabellen für alle möglichen Aufgaben in FoxPro als Entwicklungsumgebung selbst. Es ist doch einfach genial, wenn Berichte, Menüs, Formulare, Klassen und was sonst noch in DBF-Tabellen abgespeichert werden können!

Was lag also näher, als beim Grübeln über die Realisationsmöglichkeiten von Prozessteuerungen unter Visual FoxPro ebenfalls über die Speicherung der Prozessinformaionen in DBF-Tabellen nachzudenken.

1. Ansatz

Der erste Ansatz ist auch schnell fertiggedacht - man nehme eine Tabelle und speichere in jeder Zeile einen Programmabschnitt. Dann nehme man einen Programm- Quellcodeinterpreter à la CodeBlock, der die einzelnen Schritte abarbeitet und schwupp di wupp, fertig ist die Prozessablaufsteuerung.

So bestechend einfach (und größtenteils funktionsfähig!) dieser Ansatz auch zu sein scheint, so viel Programmieraufwand wäre für die Realisation notwendig, einmal ganz abgesehen davon, daß der Codeblock-Interpreter eine zusätzliche "Speed-Bremse" darstellt.

Trotzdem beinhaltet schon dieses "kleine" Konzept alles, um eine "Offene System Architektur" zu realisieren, stellt es doch (fast) alle Möglichkeiten bereit, zur Laufzeit noch Änderungen, Anpassungen und Erweiterungen an der Anwendung (am Gesamtprozeß) vorzunehmen.

DBC gestütztes Prozeßmanagement

Die im ersten Ansatz beschriebene Konstruktion kam in dieser Form noch unter FoxPro 2.6 zum Einsatz, da dort andere, besser geeignete Mechanismen noch nicht zur Verfügung standen. Mit der Einführung des Data-Dictionaries ab der Visual FoxPro Version 3.0 stehen allerdings neue, überaus nette Features bereit, die förmlich nur darauf gewartet haben, von uns für die Prozessteuerung eingespannt zu werden.

2. Ansatz

Was spricht dagegen, die verschiedenen Trigger des Visual FoxPro DBC's zu benutzen?

Hier meine Überlegungen zu diesem Thema: Wenn wir ein Objekt, das in einer Klassenbibliothek gespeichert ist, zur Laufzeit benutzen wollen, müssen wir es mit oMyObject = CREATE(.....) instanziieren. Dieser Vorgang erzeugt eine Abbildung des "Blueprints" den die Klasse darstellt im Arbeitsspeicher meiner Anwendung. Versehen mit einem Handle auf diesen Bereich im RAM ist es mir jetzt möglich auf alle PEMs zuzugreifen (vorausgesetzt diese sind nicht geschützt).

Ich möchte dieses Standardobjekt hier einmal ein "speicherbasiertes" Laufzeitobjekt nennen.

Ist es möglich Objekte auch als "tabellenbasierte" Laufzeitobjekte zu kreieren? Und was für Vorteile hätten solche "Kreationen"?

PEM steht für Properties, Events und Methods. Können Properties, Events und Methods von instanziierten Objekten auf FoxPro-Tabellen abgebildet werden?

Ein Property einer Klasse ist abbildbar auf der Spalte einer Tabelle.

Ein Event kann entweder auf dem Field-Validation-Trigger oder ggf. auch auf den Insert- oder Delete-Triggern eines (die Tabelle "umschließenden") DBCs abgebildet werden.

Eine Methode könnte wiederum auf einer Triggerkonstruktion abgebildet werden.

Die Field-Validation-Trigger feuern beim INSERT einer neuen Tabellenzeile in der Reihenfolge der Spalten "von links nach rechts". Dieses Verhalten kann dazu benutzt werden, um die Reihenfolge von Events (Event-Execution-Sequence) nachzubilden.

Mal angenommen, ich möchte eine Art "eigene Basisklassen" entwickeln. Diese Basisklasse soll die folgenden Events bei der Instanziierung der Reihe nach feuern:

INIT-Event 
LOAD-Event 
ACTIVATE-Event 
GOTFOCUS-Event 

Also könnte ich nun eine Tabelle erzeugen, die (erst einmal zum Spielen) vier Spalten hat. Die Tabellenstruktur sähe dann folgendermaßen aus:

Spaltenname Spaltentyp Länge Nachkomma Null zulässig? Validation-Trigger-Name

Init

L

1

0

Ja

InitValidTrigger()

Load

L

1

0

Ja

LoadValidTrigger()

Activate

L

1

0

Ja

ActivateValidTrigger()

GotFocus

L

1

0

Ja

GotFocusValidTrigger()

in dem dazugehörigen DBC wären die Eventroutinen in den Stored Procedures in ihrer absoluten Basisform codiert abgespeichert.

Durch Einfügen einer neuen Tabellenzeile (die Feldinhalte setze ich dabei jetzt erst einmal alle auf TRUE) "instanziere" ich eine Art "Laufzeitversion" dieser Basisklasse. Noch definiert sich die Klasse nur durch ihre vier Startup-Events, aber diese feuern wenigstens schon mal in der gewünschten Reihenfolge, nämlich Init, Load, Activate und zum Schluß GotFocus.

Nun möchte ich aber (genau wie in Visual FoxPro) das Standardverhalten durch eigenen Code ändern. Also ergänze ich die Tabellenstruktur wie folgt:

Spaltenname Spaltentyp Länge Nachkomma Null zulässig? Validation-Trigger-Name

Init

L

1

0

Ja

InitValidTrigger()

_Init

M

1

0

Ja

- keiner -

Load

L

1

0

Ja

LoadValidTrigger()

_Load

M

1

0

Ja

- keiner -

Activate

L

1

0

Ja

ActivateValidTrigger()

_Activate

M

1

0

Ja

- keiner -

GotFocus

L

1

0

Ja

GotFocusValidTrigger()

_GotFocus

M

1

0

Ja

- keiner -

und in meinen dazugehörigen DBC würde ich die Eventroutinen in den Stored Procedures dahingehend erweitern, daß z.B. ein Codeblock-Interpreter den Sourcecode aus dem korrespondierenden Memofeld abarbeiten würde, falls es nicht leer ist.

Im nächsten Schritt würde ich mir noch ein eventuell fälliges Parameterübergabeverfahren ausdenken. Z.B. dürften meinem Init-Event Parameter übergeben werden, also ändern wir kurzerhand die Struktur:

Spaltenname Spaltentyp Länge Nachkomma Null zulässig? Validation-Trigger-Name

Init

C

60

0

Ja

InitValidTrigger()

_Init

M

1

0

Ja

- keiner -

In das 60 Zeichen lange Feld können nun Variablennamen etc. eingetragen werden, die dann intern eben als Zeiger auf die Parameterwerte ausgewertet werden müssen.

Jetzt kommt die Realisation von Properties. Dies ist sehr einfach. Ich lege 2 Standard-Properties fest:

Spaltenname Spaltentyp Länge, Nach-komma Null zulässig? Validation-Trigger-Name

Init

C

60

0

Ja

InitValidTrigger()

_Init

M

1

0

Ja

- keiner -

Load

L

1

0

Ja

LoadValidTrigger()

_Load

M

1

0

Ja

- keiner -

Activate

L

1

0

Ja

ActivateValidTrigger()

_Activate

M

1

0

Ja

- keiner -

GotFocus

L

1

0

Ja

GotFocusValidTrigger()

_GotFocus

M

1

0

Ja

- keiner -

Visible

L

1

0

Nein

- keiner -

Caption

M

4

0

Nein

- keiner -

Die Defaultwerte für diese zwei Properties erhalte ich wieder aus dem DBC (dort Feldgültigkeit Standardwert).

Und zum Schluß erfinde ich noch ein bis zwei weitere Methoden, die ich genau wie eingangs die Events einbaue....

Was kann man mit dieser Konstruktion jetzt machen? Nun, man kann sich seine Gedanken über solch ein Konzept machen:

Die gerade erzeugte Tabelle mitsamt dem dazugehörigen DBC stellt jetzt eigentlich WAS(?) dar, wenn man sie mal mit einer VCX, oder gar mit einer der VFP Basisklassen vergleicht?

Nun, man könnte behaupten, daß der DBC die Basisklasse darstellt. Besser (da präziser) formuliert müßte man sagen, daß der DBC die programmtechnische Realisation der PEM-Basisiklassenfunktionalität darstellt und darüber hinaus noch weitere allgemeingültige, objektorientierte Verhaltensmuster implementiert.

Die im DBC enthaltene Tabelle ist, um beim direkten Vergleich mit den VFP Basisklassen zu bleiben, der Platz, an dem die Laufzeitinstanz der Klasse angelegt wird - also so eine Art virtueller Arbeitsspeicher <g>.

Kann man den jetzt eigentlich so eine "Klasse" subclassen?? Eigentlich schon! Wir legen einfach eine neue Tabelle mit der gleichen Tabellenstruktur an. Dies ist nun unsere Subklasse. In Zukunft "APPEND BLANKen" wir dann nicht mehr einfach einen Satz um ein Objekt zu "instanziieren", sondern übertragen einfach die Zeile unserer Subklasse komplett in die "Arbeitsspeichersimulationstabelle"! In den Memofeldern unserer Subklasse haben wir dort unseren eigenen Code eingetragen, wo wir die Vererbung unterbrechen wollten. Das Übertragen leerer Memofelder feuert ja (wie bei Standardobjekten im Original) immer noch das im DBC abgelegte Standarverhalten.

Natürlich sind da einige "kleinere" Fallstricke drin, die man aber programmatisch umschiffen kann. So muß man alle Tabellen die in dieser Konstruktion als Speichertabelle, oder Subklassentabelle mitspielen immer synchron ändern, wenn man die Struktur erweitert, um z.B. eine Subklasse um bestimmte Events zu erweitern.

Natürlich geht es mir an dieser Stelle nicht darum die Speicherung und Instanziierung von VFP-Klassen neu zu erfinden. Lediglich die Idee der Abbildbarkeit eines instanziierten Speicherobjekts in den Adressraum von VFP's DBF-Tabellen ist hier erst einmal von Bedeutung. Auf dieser Grundüberlegung bauen die meisten der nachfolgenden Konzepte auf, die benutzt werden um die Implementation eines VFP-basierten Applikations-Worflow-Managment-Systems zu evaluieren.

So viel sei hier aber schon einmal verraten. Das Feuern von Events beim Einfügen von Tabellenzeilen, oder beim Ändern von Feldinhalten geht Visual FoxPro-intern brutal schnell! Und die Abarbeitung der zugeordneten, compilierten Stored Procedures im DBC ist auch ohne jeden Preformance-Hit - mit dem CodeBlock-Schneckenpost-Iterpreter nicht zu vergleichen!

Damit jetzt diese Technik allgemeingültig eingesetzt werden kann, muß ein Speicherungskonzept "erfunden" werden, das es uns ermöglicht, beliebig viele PEMs pro Objekt (variant) zu speichern. Dazu dient die ObjectStore Technik, die ich in meiner ersten Session bereits ausführlich vorgestellt habe.

Zusammenfassung

Persistente Objekte können als Record-Sets in Tabellen eines DBC's abgebildet werden. Mit den entsprechenden Methoden und Trigger-Routinen läßt sich eine vorwärtsgerichtete Wiederherstellung des Speicherobjekts realisieren.