Dienstag, 15. April 2014

Optimierung durch Entity Abstraktion - Teil I Datenbankdesign

Beim Datenbankdesign hat man oft hauptsächlich die Daten vor Augen. Es wird geprüft, ob die Daten Redundanzen enthalten. Wenn ja, führt man die üblichen Normalisierungen durch. An die Implementierung denkt man in der Regel dabei noch nicht oder nur selten. Meistens nimmt man an, daß sich aus den richtigen Datenstrukturen in der Datenbank die passenden Datenstrukturen für die Implementierung einfach ergeben. Dass das nur die halbe Wahrheit ist, möchte ich in diesem Artikel zeigen.

Ich arbeite für eine bekannte Wetterseite. Aus diesem Umfeld möchte ich auch das Beispiel entlehnen. Es geht um Wetterberichte. Diese erhalten wir als XML Dateien. Es gibt z.Z. ca. 20 verschiedene Wetterberichte. Für meine Überlegungen reichen drei Typen aus und die stelle ich hier auch nur vereinfacht dar.

Die Wetterdaten


Da wäre zunächst der Kurzwetterbericht wetterbericht_kurz.xml mit folgender Struktur:

1:  <?xml version="1.0" encoding="UTF-8"?>   
2:   <wt_kurz>   
3:    <datumzeit>   
4:     <datum>2013-11-10</datum>   
5:     <zeit>12:00:00 </zeit>   
6:    </datumzeit>   
7:    <titel>Langwetter Nachrichten</titel>   
8:    <ueberschrift>Wetterbericht fuer Bayern, Sonntag 15. Juli, 12:00 Uhr</ueberschrift>   
9:    <kopfzeile>Im Westen regnerisch, im Osten anfangs noch warm</kopfzeile>   
10:    <ueberschrift_wt_text>Das Wetter in Bayern:</ueberschrift_wt_text>   
11:    <wt_text>Hier steht jetzt der Wettertext ... blalalalallala</wt_text>   
12:    <ueberschrift_wt_naechste_tage>Die weiteren Aussichten bis Donnerstag:</ueberschrift_wt_naechste_tage>   
13:    <wt_naechste_tage>und auch in den kommenden Tagen viel Wetter .... </wt_naechste_tage>   
14:    <fuss>MeteoInfo: Meteorologe vom Dienst: Tel: 089 / 123456</fuss>   
15:   </wt_kurz>   



Dann der Langwetterbericht wetterbericht_lang.xml:
1:  <?xml version="1.0" encoding="UTF-8"?>   
2:   <wt_lang>   
3:    <datumzeit>   
4:      <datum>2013-11-10</datum>   
5:      <zeit>12:00:00 </zeit>   
6:    </datumzeit>   
7:    <titel>Langwetter Nachrichten</titel>   
8:    <ueberschrift>Wetterbericht fuer Bayern, Sonntag 11. November 12 Uhr </ueberschrift>   
9:    <kopfzeile>Im Westen regnerisch, im Osten anfangs noch warm</kopfzeile>   
10:    <ueberschrift_wt_lage>Die Vorhersage für Bayern bis morgen früh</ueberschrift_wt_lage>  
11:    <wt_lage>Ein Regengebiet zieht von Westen her nach Bayern</wt_lage>   
12:    <ueberschrift_wt_text_1>Wetter bis zum Abend </ueberschrift_wt_text_1>   
13:    <wt_text_1>Im Südosten am Vormittag nochmals recht freundlich und Temperaturanstieg auf 13 bis 18 Grad, nachmittags und abends dann teils kräftige Schauer und Gewitter mit nachfolgendem Temperatursturz. Ansonsten herrscht heute starke Bewölkung und es regnet immer wieder bei Höchstwerten nur um 18 Grad. Nachts bewölkt und gebietsweise Regen; Tiefstwerte um 2 Grad.</wt_text_1>   
14:    <ueberschrift_wt_text_2>Wetter in der Nacht</ueberschrift_wt_text_2>   
15:    <wt_text_2>In der Nacht verbreitet Nebel. Später gibt es strichweise Regen. Tornados nur im Westen.</wt_text_2>   
16:    <ueberschrift_wt_naechste_tage>Die weiteren Aussichten bis Donnerstag</ueberschrift_wt_naechste_tage>   
17:    <wt_naechste_tage>Morgen auch unbeständiges Wetter und die nächsten Tage mehr Sonne.</wt_naechste_tage>   
18:    <ueberschrift_wt_berge>Bergwetter:</ueberschrift_wt_berge>   
19:    <wt_berge>In den Bergen ab 1500m Schneefall möglich.</wt_berge>   
20:    <fuss>MeteoInfo: Meteorologe vom Dienst: Tel: 089 / 123456</fuss>   
21:   </wt_lang>   

Als drittes Beispiel der 7-Tage Wetterbericht wetterbericht_7tage.xml:
1:  <?xml version="1.0" encoding="UTF-8"?>   
2:   <wt_7tage>   
3:    <datumzeit>   
4:      <datum>2005-10-24</datum>   
5:      <zeit>12:00:00</zeit>   
6:    </datumzeit>   
7:    <titel>7-Tages-Vorschau</titel>   
8:    <tx_tag1>Am Mittwoch ist es in Nordbayern über-wiegend bewölkt und es fällt gelegent-lich   
9:    Sprühregen oder geringfügiger Regen. Im Süden wechseln Wolken mit län-geren sonnigen Abschnitten.   
10:    Tiefstwerte zwischen 13 Grad in Unterfranken und 6 Grad in einigen Alpentä-lern, Höchstwerte   
11:    je nach Sonnenschein zwischen 15 und 20 Grad.</tx_tag1>   
12:     <tx_tag2>Am Donnerstag mehr Sonne und leicht steigende Temperaturen. Kaum noch Regen.</wt_tag2>   
13:     <tx_tag3bis7>Von Freitag bis Dienstag ist es unter überwiegendem Hochdruckeinfluss meist freundlich;   
14:     in den Niederungen, besonders an der Donau, kann es jeweils vormittags länger neblig-trüb sein.  
15:     Nachts zwischen 10 und 4 Grad, Nachmit-tagstemperaturen meist zwischen 15 und 20 Grad.   
16:     Am Montag ist es wechselnd bewölkt mit längeren sonnigen Abschnitten, in Franken sind   
17:     vereinzelt Schauer möglich.    
18:     Tiefsttemperaturen 10 bis 5, Höchstwer-te 13 bis 17 Grad.</tx_tag3bis7>   
19:     <fuss>WetterInfo: Meteorologe vom Dienst, Tel: 089 / 1234567</fuss>   
20:   </wt_7tage>   

Konventionelle Datenbankstruktur


Wenn man die Daten auschaut, ist eine gewisse Ähnlichkeit vorhanden, aber letztlich muß man jeden Bericht als eigene Entität betrachten.
Physikalisch erhalten wir daher 3 Tabellen.
  •  Wetterbericht kurz: wt_bericht_kurz 
  •  Wetterbericht lang: wt_bericht_lang 
  •  Wetterbericht 7 Tage: wt_bericht_7tage
In SQL erhalten wir also:

1:  create table wt_bericht_kurz(  
2:    bericht_von          datetime not null,  
3:    titel             varchar(500),  
4:    ueberschrift          varchar(500),  
5:    kopfzeile           varchar(500),  
6:    ueberschrift_wt_text      varchar(500),  
7:    wt_text            varchar(4000),  
8:    ueberschrift_wt_naechste_tage varchar(500),  
9:    wt_naechste_tage        varchar(4000),  
10:    fuss              varchar(500)  
11:  );  
12:  create table wt_bericht_lang(  
13:    bericht_von          datetime not null,  
14:    titel             varchar(500),  
15:    ueberschrift          varchar(500),  
16:    kopfzeile           varchar(500),  
17:    ueberschrift_wt_lage      varchar(500),  
18:    wt_lage            varchar(4000),  
19:    ueberschrift_wt_text_1     varchar(500),  
20:    wt_text_1           varchar(4000),  
21:    ueberschrift_wt_text_2     varchar(500),  
22:    wt_text_2           varchar(4000),  
23:    ueberschrift_wt_naechste_tage varchar(500),  
24:    wt_naechste_tage        varchar(4000),  
25:    ueberschrift_wt_berge     varchar(500),  
26:    wt_berge            varchar(4000),  
27:    fuss              varchar(500)  
28:  );  
29:  create table wt_bericht_7tage(  
30:    bericht_von          datetime not null,  
31:    titel             varchar(500),  
32:    ueberschrift          varchar(500),  
33:    tx_tag1            varchar(4000),  
34:    tx_tag2            varchar(4000),  
35:    tx_tag3bis7          varchar(4000),  
36:    fuss              varchar(500)  
37:  );  

Datenbankstrukturen mit Entity Abstraktion


Gibt es eine Alternative zum klassischen DB Design? Wir haben ca. 20 verschiedene Berichte. Sie sind ziemlich ähnlich von der Struktur und völlig analog bei der Verarbeitung und beim Rendering.

Im klassischen Ansatz entspricht jeder Bericht einer Entität. Begründung: Jeder Bericht hat eine andere Struktur. Es gibt keine Möglichkeit die Datenbankstruktur zu verallgemeinern! Keine Möglichkeit?

Betrachten wir einmal die Berichte von einem erhöhten Standpunkt aus. Was haben alle Berichte gemeinsam? Alle Berichte haben einen Erstellungszeitpunkt. Einige Elemente wiederholen sich. Beispiel: Kopf- und Fußzeile. Aber irgendwie reicht das nicht. Versuchen wir noch weiter nach oben zu steigen, bis wir die Einzelheiten der Berichte nicht mehr erkennen können.

Was zeigt sich? Es gibt eine Gemeinsamkeit ALLER Berichte! Man kann sogar von einer indentischen Struktur ALLER Berichte sprechen.

Alle Berichte bestehen aus einem Erstellungsdatum, einem Typ und einer Liste von Berichtstexten. Was sind Berichtstexte?

Jeder Bericht ist letztlich eine Liste von Texten. So wird auch beim Rendering vorgegangen. Ein Detail ist jedoch wichtig. Die Texte werden unterschiedlich gerendert. Das bedeutet jeder Text hat einen Typ, z.B. Überschrift, normaler Text, Zwischenüberschrift, Kopf- und Fußzeile.

Was aber kann man aus dieser Betrachtung für das Datenmodell ableiten? Im konventionellen Design besteht ein Bericht aus einzelnen Elementen, die auf einzelne Spalten einer Tabelle abgebildet werden. Bei genauer Betrachtung zeigt sich aber, daß alle diese Spalten vollständig durch zwei Eigenschaften charakterisiert sind.
  • Es sind Texte
  • Sie haben einen bestimmten Typ - Überschrift, Normaltext (s.o.). 
Was passiert, wenn man sagt, daß alle diese Texte eine eigene Entität bilden? Es entsteht eine neue Betrachtungsebene, d.h. man führt eine Abstraktion durch. Aus den Tabellenspalten der konventionellen Berichtstabellen wird eine eigene Entität.
Wir wollen diese Entität hier als Berichtstexte bezeichnen. Welche Eigenschaften hat diese Entität?
  • Text der des Berichtstextes
  • Typ des Berichtstextes
  • Wetterbericht, zu dem dieser Berichtstexte gehört, d.h. hier steht eine ID
Was passiert mit den Berichten?
Auch hier gibt es eine Entität mit folgenden Eigenschaften:
  • Typ des Berichts
  • Erstellungstermin
  • ID des Berichts
Da wir natürlich den Bericht und die Berichtstexte verknüpfen wollen, muß eine ID eingeführt werden.

Wir erhalten folgendes SQL:

1:  create table bericht(  
2:    berichts_id   bigint(20) not null,  
3:    bericht_von   datetime not null,  
4:    berichts_typ  varchar(30) not null,  
5:    PRIMARY KEY ( berichts_id )  
6:  );  
7:  create table berichts_texte(  
8:    berichts_id   bigint(20) not null,  
9:    text_typ    varchar(30) not null,  
10:    inhalt     varchar(4000) not null,  
11:    PRIMARY KEY ( berichts_id, text_typ ),  
12:    KEY fk_berichts_id ( berichts_id ),  
13:    CONSTRAINT fk_berichts_id FOREIGN KEY ( berichts_id )   
14:     REFERENCES bericht ( berichts_id )  
15:  );    

Diese Tabellen reichen aus, um ALLE möglichen Berichte abzubilden.

Vergleich der Datenmodelle


Schon auf der Ebene des Datenbankmodells zeigen sich grundsätzliche Unterschiede zwischen dem klassischen Ansatz und der Entity Abstraktion. Während wir im ersten Fall für jeden neuen Bericht eine neue Entität brauchen, deckt im zweite Fall das Design mit zwei Entitäten alle Berichte ab. In unserem konreten Fall haben wir 3 Berichte und deshalb auch 3 Tabellen, die den 2 Tabellen aus der Entity Abstraktion gegenübestehen.

Wie sieht es mit den Abfragen aus?

Im klassischen Ansatz haben wir für jede Tabelle eigene Abfragen. Also in SQL:

1:  select * from wt_bericht_kurz where bericht_von = '2014-02-24 00:00:00';  
2:  select * from wt_bericht_lang where bericht_von = '2014-02-24 00:00:00';  
3:  select * from wt_bericht_7tage where bericht_von = '2014-02-24 00:00:00';  

Bei der Entity Abstraktion erhalten wir ein komplexeres SQL:

1:  select * from berichts_texte a, bericht b  
2:  where   
3:    a.berichts_id = b.berichts_id   
4:    and  
5:    b.berichts_typ = 'bericht_kurz'  
6:    and   
7:    b.bericht_von = '2014-02-24 00:00:00';  

Diese zusätzliche Komplexität ist natürlich durch die Abstraktion verursacht. Die klassischen SQL Statements hingegen sind sofort verständlich.
Der große Vorteil der Abstraktion zeigt sich in der Anwendung auf viele verschiedene Berichte.

Die klassische Lösung braucht für jeden Bericht eine eigene SQL Abfrage. Bei abstraktem Design haben wir aber nur eine Abfrage, der als Parameter der Typ mitgegeben wird.

Je mehr verschiedene Berichte es gibt, desto signifikanter wird der Vorteil. Erscheint bei 3 Berichten der Unterschied nur gering, ist er bei 10 Berichten schon sehr große. Jeder neue Bericht erfordert eine eigene Implementierung.

Welchen Vorteil die abstrakte Abfrage bietet, zweigt sich auch bei komplexeren Abfragen. Angenommen wir wollen ein Skript schreiben mit dem man in allen Berichten nach dem Wort 'Gewitter' suchen kann. Die Lösung beim abstrakten Desigen ist simpel:

1:  select * from berichts_texte a, bericht b  
2:  where   
3:    a.berichts_id = b.berichts_id   
4:    and  
5:    b.berichts_typ = 'bericht_kurz'  
6:    and   
7:    b.bericht_von = '2014-02-24 00:00:00'  
8:    and  
9:    a.berichts_text like '%Gewitter%'  
10:  ;  

Beim klassischen Design spürt man sofort die Schwerfälligkeit und Unflexibilität die damit verbunden ist.

1:  select * from wt_bericht_kurz  
2:  where   
3:    titel like '%Gewitter%' and  
4:    ueberschrift like '%Gewitter%' and  
5:    kopfzeilelike '%Gewitter%' and  
6:    ueberschrift_wt_textlike '%Gewitter%' and  
7:    wt_text like '%Gewitter%' and  
8:    ueberschrift_wt_naechste_tagelike '%Gewitter%' and  
9:    wt_naechste_tage like '%Gewitter%' and  
10:    fuss like '%Gewitter%';  
11:  select * from table wt_bericht_lang  
12:  where  
13:    titel like '%Gewitter%' and  
14:    ueberschrift like '%Gewitter%' and  
15:    kopfzeile like '%Gewitter%' and  
16:    ueberschrift_wt_lage like '%Gewitter%' and  
17:    wt_lage like '%Gewitter%' and  
18:    ueberschrift_wt_text_1 like '%Gewitter%' and  
19:    wt_text_1 like '%Gewitter%' and  
20:    ueberschrift_wt_text_2 like '%Gewitter%' and  
21:    wt_text_2 like '%Gewitter%' and  
22:    ueberschrift_wt_naechste_tage like '%Gewitter%' and  
23:    wt_naechste_tage like '%Gewitter%' and  
24:    ueberschrift_wt_berge like '%Gewitter%' and  
25:    wt_berge like '%Gewitter%' and  
26:    fuss like '%Gewitter%'  
27:  );  

Die Abfrage für wt_bericht_7tage kann hier entfallen. Schon diese zwei SQL Befehle zeigen die erheblichen Einschränkungen. Man stelle sich vor wird hätten hier 10 Berichte. Der Unterschied wäre wirklich dramatisch.

Welches SQL Statement ist hier nun besser lesbar? Der Unterschied ist offensichtlich. Hier spielt der abstrakte Ansatz seine Stärken voll aus.

Natürlich wird ein Vertreter des konventionellen Designs fragen, ist das nicht nur ein theoretischer Vorteil? Wer braucht schon eine Suche über alle Felder eines Berichts?

Es mag Fälle geben, in den man das nicht braucht. Da jedoch ein Bericht als solcher, eine logische Einheit ist, besteht immer ein wichtiger möglicher Anwendungsfall darin, daß man irgendetwas über den ganzen Bericht sucht. Da die Anforderungen an ein Projekt sich im Laufe der Zeit ändern können, bietet das abstrakte Design hier eine große Flexibilität. Diese fehlt jedoch dem klassischen Design vollkommen.

Diese allein durch das Design verursacht Flexibilität oder Unflexibilität wird sich wie ein roter Faden durch die weiteren Überlegungen ziehen. Schon an dieser Stelle zeigt sich jedoch, wie tief Datenbankdesign und Implementierung verbunden sind.

Putzfrauen und Abstraktionen

Gerade Entwickler die bisher ehr an Projekten mit geringerer Komplexität beteiligt gewesen sind, fragen sich, ob nicht die Kosten, die durch die zusätzliche Abstraktionsebene auf der Seite des Verständnisses für sie entstehen nicht zu hoch sind.  Es ist so wie immer in der Softwareentwicklung, was man nicht kennt, kann man nicht schätzen. Am Anfang sieht man nur: Das läßt sich deutlich schlechter verstehen als bisher. Wenn man sich jedoch mit der Abstraktion näher beschäftigt und sieht, welche neuen Möglichkeiten dadurch entstehen und wie redundante Sachverhalte nichtredundant, einfach und elegant abgebildet werden können, wird diese Sicht der Dinge nicht mehr missen wollen. Dieser Paradigmenwechsel hat es in sich. Es ist als ob man von einem Fahrrad auf ein Motorad umsteigt.

Sicher Abstraktion ist am Anfang etwas mühsam. Ich hatte mal einen Chef, der sagte immer zu uns Entwicklern: Wir haben soviel zu tun, daß wir auch die Putzfrau mitentwickeln lassen sollten. Vermutlich werden sich die meisten Putzfrauen aber sehr, sehr schwer tun die oben besprochenen Abstraktionen zu verstehen oder sogar selbst welche zu erstellen. Für einen guten Softwareentwickler hingegen, sollte das kein Problem sein. Und sind wir nicht alle 'gute' Softwareentwickler? ;-) Der Anfang ist sicher mühsam. Aber auch ein Fahrradfahrer der Motoradfahren lernt, muß sich zunächst etwas abmühen. Der dadurch erzielte Gewinn entlohnt aber sehr reichlich die Anstrengungen!

Entwickler, die selbst die Entity Abstraktion verwenden, sollten nicht enttäuscht sein, wenn sie bei anderen Kollegen zunächst auf Ablehnung stoßen. Man muß hier u.U. sehr viel Überzeugungsarbeit leisten. Am Anfang schaut alles nur komplizierter aus als vorher. Die eigentlichen Vorteile kommen erst später.

Keine Kommentare:

Kommentar veröffentlichen