Mittwoch, 22. Juni 2011

Datenbankzugriffe vereinfachen und Effizienz beim Entwickeln schaffen

Hier will ich eine sehr einfache und doch effiziente Beschreibung von Möglichkeiten bieten, die ein Entwickler mit dem Umgang der Datenbank beachten sollte. Hier wird auf der Applikationsebene diskutiert. Eine Performance-Steigerung auf der Datenbank-Ebene wird mit diesem Post nicht behandelt.

Problem
Man entwickelt fast immer eine Webapplikation mit Datenbank-Zugriff und muss dann immer wieder die gleichen Funktionen einbinden, damit der Zugriff klappt. Dabei benötigt nicht jede Web-Applikation zwingend eine Datenbank (DB). Dann die Tabellen installieren und eventuell mit Daten vorbelegen. Die Anwendung bearbeitet die DB. Oft wird eine Applikation auch ohne DB-Zugriff aufgerufen, zum Beispiel bei kleineren Aufgaben (Loginmaske zeigen). Hier wirkt der am Anfang eingebundene DB-Verbindungsaufruf störend, bzw mindert die Performance. Gerade gut laufende Applikationen werden immer wieder erweitert. Diese Erweiterungen sind oft mit DB-Manipulationen behaftet. Nun muss der Entwickler Update-Pakete schnüren, welche auch noch die DB anpassen. Die Komplexität beim Update, bzw der Installation wächst in Form von Installationsschritten, die der Betreiber aktiv durchführen muss. Dazu kann es passieren, dass man eventuell die DB wechseln muss und dann alle Funktionsaufrufe, welche auf eine bestimmte DB zugeschnitten sind, ändern. Bei grossen Applikationen kann dies schon sehr aufwendig sein. Gerade DB-Zugriffe sind oft vom gleichen Schema: Daten holen, Daten in Array packen, Daten durchgehen. Diese relative formale Angelegenheit, verlängert nur den Code. Wenn man dann noch Messungen oder andere vor- bzw. nachgelagerte Funktionen ausführen möchte, werden auch diese zu wiederholenden Elementen, die den Code verlängern und damit ebenfalls zu Lasten der Performance gehen. Hier sei angemerkt, dass nicht jeder kurze Code Performance bringt und sich auf wiederholenden Ausführungen von Codezeilen bezieht.

Idee
Um die möglichen Probleme in den Griff zu bekommen bietet es sich von je her an, die DB-Zugriffe zu kapseln. Alter Hut und dennoch immer wieder neu. Durch die Kapselung der eigentlichen DB-Zugriffe vermindern wir die Performance, da mindestens ein weiterer Funktionsaufruf erzeugt wird. Dennoch gewinnen wir viel an Flexibilität und Entwicklungsgeschwindigkeit. Das Kapseln der Funktionen bedeutet, dass wir eine Wrapper-Funktion, um den eigentlichen Aufruf legen. In unserem Fall eine Funktion, die dann die richtige Funktion aufruft, nach dem einige Vorbereitungen getroffen wurden. Und das Ergebnis zurück liefert, nachdem einige Manipulationen vorgenommen wurden. Diese Wrapper-Funktionen sind auch ideal, um die eigentlichen Aufrufe zu zählen und damit Aufschlüsse über die Performance der Applikation an sich zu geben. Desweiteren wollen wir noch, dass der Verbindungsaufbau nur dann geschieht, wenn wir die DB wirklich brauchen. Dies können wir ideal in der Wrapper-Funktion einbinden. Da bei einem Fehler, die Wrapper-Funktion dies erfährt, können wir auch hier die Fehlerbehandlung integrieren und entsprechend agieren. Dafür binden wir hier eine  automatisierte Reparatur-Funktion ein, welche Änderungen an der DB entsprechend vornimmt. Der Anwender bekommt davon nichts mit und die Installation ist spielend einfach und kurz gehalten. Sollte sich nun ein DB-Wechsel anbahnen, muss man lediglich die entsprechende Änderung in der Wrapper-Funktion vornehmen. An dieser Stelle ist ein Ausbau möglich, so dass man gleich die verschiedenen DB-Systeme integriert. Für diesen Post reicht erstmal der Zugriff auf MySQL, da gerade bei der Auswertung der Fehler, noch einiges an Code auf uns zu kommen würde. Dieser aber nur einmal und dann landen diese Funktionen in unser Framework. Somit lohnt sich der Ausbau für den Leser schon. Jeder Entwickler baut im Laufe der Zeit ein eigenes Framework, welches er immer wieder für seine neuen Projekte nutzt.

Umsetzung
Um Klassen zu vermeiden, werden globale Funktionen eingesetzt. Es ist jedem selbst überlassen, wie er seine Wrapper erstellt. Wobei man zugeben muss, dass gerade diese Arbeit wunderbar in einer Klasse realisiert werden könnte. Derzeit traue ich dem Klassen-System von PHP noch nicht soweit, dass es zukunftssicher gelten kann (also auch in 4 Jahren noch so ist, wie derzeit) und verwende Funktionen mit angepassten Namen und globale Variablenzugriffe, damit diese Beispiel-Funktionen immer funktionieren. Der Zugriff erfolgt auf MySQL mittels PHP.
Wir wrappen folgende Funktionen:

  • mysql_connect
  • mysql_insert_id
  • mysql_query
  • mysql_affected_rows
Dazu benötigen wir noch ein paar Hilfsfunktionen, um die Daten aus der DB zu holen und für den weiteren Gebrauch aufzubereiten. Ferner speichern wir die Änderung der DB in eine separate Funktion, die wir mittels "include" erst einladen, wenn wirklich benötigt. Die Funktion wird in diesem Beispiel simple gehalten und enthält die entsprechenden SQL Befehle. Man könnte auch sehr gut die Struktur der Datenbank als Array oder anderweitig ablegen und dann dynamisch die SQL erstellen lassen. Das hätte auch den zusätzlichen Aspekt der Unabhängigkeit von der DB-Software, würde aber den Grund des Posts zu stark erweitern.

Als globale Variable für die DB-Zugriffe definieren wir einen assoziativen Array, der in der Konfigurationsdatei durch den Betreiber gefüllt wird. Diese Variable sollte zu Beginn oder in einer Konfigurationsvariable gesetzt werden.


$system = array();
$system['db']['dbuser'] = "DBuser";
$system['db']['dbuserpw'] = "DBpasswort";
$system['db']['dbname'] = "DBname";
// prefix wird an die Tabellennamen vorangestellt,
// so kann man eine DB mit verschiedenen Tabellen  und
// Applikationen nutzen. Man muss nur auf die Eindeutigkeit
// des Prefix pro Applikation achten
$system['db']['prefix'] = "meinPrefix_";
$system['db']['dbserver'] = "localhost";
$system['db']['dbserverport'] = "3306";
$system['db']['connected'] = false;  // hilft beim Erkennen, ob DB schon verbunden

Im Gegensatz zum klassischen Entwurf, binden wir die DB-Initialisierung nicht in den PHP Code ein. Beim ersten Aufruf einer DB-Funktion wird die Verbindung aufgebaut. Somit kann man frei entwickeln und die DB erst einbauen, wenn Sie wirklich gebraucht wird. Sehr gut auch für Prototypen-Bau. So kann man die DB unkompliziert einbinden und wieder rückstandlos entfernen. Die folgenden Funktionen sollte man direkt in sein Framework einbinden. So kann man diese immer nutzen, wenn nötig.

// stellt die Verbindung mit DB her
// hier wird absichtlich nicht geprüft "$system[db][connected] == true", 
// so kann man eine Verbindung nochmal provozieren
function _db_connect() {
  global $system;
 $dbv = $system[db][dbserver].":".$system[db][dbserverport];
  // Datenbank verbinden
  $dbcon = mysql_connect($dbv, $system[db][dbuser], $system[db][dbuserpw]);
  if (!$dbcon)
    die("Problem in der Datenbankverbindung");
  // Datenbank vorauswaehlen
  mysql_select_db($system[db][dbname]);
  $system[db][connected] = true;  // verbindung steht aufschreiben
  return $dbcon;
}

// die anzahl der betroffenen Zeilen
function _db_num_rows() {
return mysql_affected_rows();
}

// liefert die ID der letzten INSERT Operation auf der DB
// die $dbcon, kann genutzt werden, 
// wenn das Skript mehrere verschiedene DB-Verbindungen aufbauen muss
function _db_sql_insert_id($dbcon) {    
  if ($dbcon == 0):
    return mysql_insert_id();
  else:
    return mysql_insert_id($dbcon);
  endif;
}

// diese Funktion kann man in eine separate Datei auslagern und 
// erst einladen , wenn benötigt. 
// sie enthält die DB Struktur und wird aufgerufen, bei einem DB Fehler
// so repariert sich das System alleine
// man könnte hier statt die SQL direkt auch generische Informationen ablegen und
// die SQL Befehle zusammen bauen, damit wäre man noch unabhängiger von DB
// die globale Variable $system und der Anteil "tabellen" enthält die Tabellennamen,
// damit das System automatisch den Prefix setzen kann und man einfachere Namen verwenden kann
// als im System genutzt.
function _system_installiereTabellen($force=false)
{
global $system;

$sql = "CREATE TABLE ".$system['db']['tabellen']['besitzer']." (
id int(32) unsigned NOT NULL auto_increment,
PRIMARY KEY (id))";
        // mit gesetztem $nichtnochmal flag aufrufen, um endlosschleifen zu vermeiden
  _db_query($sql, 0, -1); 
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add start int(32) unsigned NOT NULL";
  _db_query($sql, 0, -1);
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add email varchar(250) NOT NULL";
  _db_query($sql, 0, -1);
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add password varchar(250) NOT NULL";
  _db_query($sql, 0, -1);
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add sessionid varchar(32) NOT NULL";
  _db_query($sql, 0, -1);
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add aktiv int(1) unsigned NOT NULL";
  _db_query($sql, 0, -1);
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add unique index idx1 (email)";
  _db_query($sql, 0, -1);
$sql = "alter table ".$system['db']['tabellen']['besitzer']." add index idx2 (sessionid)";
  _db_query($sql, 0, -1);
}

// hiermit wird die eigentliche SQL an DB gesendet
// nutzt man, um die SQL direkt zu senden, ohne einen Rückgabewert zu erwarten, 
// ausser der last insert ID. Also bei NICHT "select"
// die $dbcon, wird genutzt, wenn mehrere DB-Verbindungen stehen
// $nichtnochmal hilft gegen rekursive endlos-Schleifen
function _db_query($sql, $dbcon=0, $nichtnochmal=0)
{
  global $system;

  // steht eine Verbindung schon ? 
  // müsste noch um die Abfrage der dbcon erweitert werden.
  if ($system[db][connected] == false)
    _db_connect();
        
  // wurde ein Datenbankkonnektor-Resource mitgegeben (Bei mehreren Verbindungen)
  if ($dbcon == 0):
    $erg = mysql_query($sql);
  else:
    $erg = mysql_query($sql, $dbcon);
   endif;

  // prüfen, ob ein INSERT gemacht wurde und liefere dann die last-insert-id
  if ($erg):
    if (strtolower(substr($sql, 0, 6)) == "insert")
      return _db_sql_insert_id($dbcon);
    return $erg;
  endif;

    // fehler prüfen
    $errno = mysql_errno();
    if ($nichtnochmal > 0):
      if (function_exists("_db_fehler_melden"))
        _db_fehler_melden("Fehler bei der SQL-Anweisung:<br>".($sql)."<br>".$errno." : ".mysql_error()."<p>");
      return 0;
    elseif ($nichtnochmal == 0):
      // hier muesste noch das protokollieren der SQL_Fehler erfolgen
      switch($errno) {
case 1054: // feld existiert nicht
case 1064:  // fehler in der SQL-Abfrage
case 1146: // tabelle existiert nicht
          // ladePHPDatei("_system_db.php");
          _system_installiereTabellen(true);
  return _db_query($sql, $dbcon, 1); // nicht noch mal, wenn es wieder schief geht
  break;
case 1046: // keine datenbank ausgewählt
          die(mysql_error());
  break;
        default:    // alles andere ignorieren
          return $erg;
     } // ende switch
    endif;

  return $erg;
}

// SQL Abfrage senden und die Daten abholen und gleich aufbereitet zurück geben
function _db_datenholen($sql) {
  $werte = array();
  $r = _db_query($sql);
if ($r):
while($daten = mysql_fetch_array($r, MYSQL_ASSOC)):
                   // prüfen, ob der User nicht schon die Verbindung vom Browser zum Server
                    // geschlossen hat. Dann könnte man aufhören
                    // manchmal ist das aber auf keinen Fall gewünscht
                    // (updates mit vorherigen Daten zum Beispiel)
   if (!$system['dontbreakevenconectionaborted'] && connection_aborted())
       exit;
$werte[] = $daten;
endwhile;
else:
   echo $sql;
endif;
return $werte;  // die daten als assoziativen Array (hash) zurück liefern
}

Beispiel
Bindet die notwendigen Framework Dateien ein und setzt eine Abfrage ab.
In unserem Beispiel werden, wenn die DB-Zugriffs-Daten hinterlegt wurden, auch gleich die Tabellen angelegt und zuvor die DB verbunden.  Die echo Anweisung wird leer sein, da ja keine Daten in der Tabelle hinterlegt sind. Man könnte dort einen "INSERT"-SQL hinterlegen und den ersten User (default-Admin) oder dergleichen anlegen lassen. Dieser wird beim Bestehen auch nicht gelöscht, da INSERT nur fehlschlagen würde.

<?php
include ("dbvar.php"); // enthält die $system Variable definition
include ("fkt.php"); // enthält das Framework, also die _db_funktionen etc

$sql = "select * from ".$system[db][tabellen][besitzer]." where id = 1";
$daten = _db_datenholen($sql);

echo $daten[0][email];
?>

Fazit
Mit diesen wenigen Code-Stücken kann man in kürzester Zeit eine Applikation schaffen und muss sich über die DB-Zugriffe keine Sorgen mehr machen. Sobald die richtigen Daten in der $system[db] eingetragen sind, kann man die Codezeilen stark reduzieren und hat schnell DB-angebundene Applikationen geschaffen. Grundsätzlich sollte man immer nach einer Vereinfachung suchen und vorallem nach etablierten wiederverwertbaren Code-Elementen Ausschau halten. Mach dein Leben einfacher. Dadurch sinkt die Fehlerquote, da dieser Code immer wieder verwendet wird und so schon erprobt ist und erhöht die Entwicklungsgeschwindigkeit von Applikationen. Jedoch sollte man darauf achten, dass nicht das Framework selbst irgendwann zum Performance Fresser wird, weil es so gross geworden ist. Ich überlege sehr lange, ob es sich lohnt eine Funktion in mein Basis-PHP-Framework einzubinden. Da diese Basis-Datei bei allen Applikationen eingebunden wird. Die Funktionen müssen oft genutzt werden und mir auch eine Erleichterung und einen Geschwindigkeitsvorteil beim Entwickeln bringen. Sollte eine Verbesserung an dieser Datei vorgenommen werden, so profitieren gleich alle Projekte davon.

Saso Nikolov

Sonntag, 12. Juni 2011

Gedanken zum Recruitment von leitenden Angestellten

Derzeit sind die Firmen wieder am wachsen und bauen weitere Abteilungen auf, bzw. erweitern bestehende.
Aufgrund des vermeintlichen gross wirkenden Bewerberansturm werden oft nur auf Zahlen und Text bezogene Kandidaten ausgefiltert. Das mag sehr sinnvoll sein, doch gibt es oft auch verstecktes Potential, welches bescheidener auftritt.

Gerade jetzt, wo man Nachhaltigkeit predigt und eine längere und gesunde Beziehung zu seinen Mitarbeiter pflegen möchte, sollte man auch an die Möglichkeit denken, selbst Führungskräfte auszubilden.

Oft werden jedoch schnell gute Erfolge verlangt und dadurch ist die Verlockung gross nur Experten mit Berufserfahrung einzustellen. Diese Experten bestechen durch Fachwissen und Berufserfahrung und wollen oft hoch hinaus. Man muss sich hierbei fragen, wieso diese Person nicht mehr bei Ihrer bisherigen Firma bleiben wollen. Wohin sie noch wollen und wie deren Einstellung bzgl Corporate Governance und Nachhaltigkeit geprägt ist. Verfolgen die Kandidaten auch ähnliche Ziele wie der neue Arbeitgeben? Hat man die gleiche Einstellung zu Produkt, Serivce und Kunde?

Zweifelsfrei ist ein Expertenteam viel wert, unschätzbar sind hingegen eigene heran gezogene Experten.
Sie vereinen die Anschauungen und Credo der Firma. Sie leben die Firmenpolitik.

Ich bin der Aufassung, dass man ein geteiltes Aufstellen des Team in Betracht ziehen sollte. Auf 2-3 extern hinzugekommene Experten, kann man eine Nachwuchskraft aufnehmen und nach seinen Bedürfnissen bilden.
Die ist zwar zeitintensiv und am Anfang auch mit erhöhten Kosten verbunden, doch im Bezug auf Nachhaltigkeit und zukunftsorientiertem Wirken ein geringer Preis angesichts dem Endergbenis und den Früchten die man ernten wird.

Bei der hier anvisierten Ausbildung ist nicht die eh obligatorische ständige Fortbildung der Mitarbeiter gemeint, sondern vielmehr das gezielte Mentoring und on-the-job-Training. Wichtig sind die Vermittlung von Unternehmenszielen und Firmenwerte, sowie díe Einstellung zu den Stakeholder ansich. Der erste Arbeitgeber ist oft prägend.

Auf welche Personen kann man achten? Sicherlich auf ganz junge für Traineeprogramme. Es gibt auch Positionen, welche eine Erfahrung benötigen. Hier kann man nach Leute achten, die lern- und wissbegierig sind und schon Berufserfahrung in Bereiche gesammelt haben, die der Stelle entsprechen. Dabei können Einflüsse aus anderen Bereichen oft hilfreich bei Entscheidungen und Einschätzungen von Problemen sein. Es kann sehr wertvoll sein, da das explizit benötigte Fachwissen on-the-job auch vermittelt wird.

Fazit
Also Mut und bauen Sie ihre Fachbereiche nicht nur auf extern erlerntes Führungswissen auf. Nutzen Sie die Chance Nachhaltigkeit und ihr Werteverständnis auf ihr Personal zu übertragen. Schaffen Sie eine zukunftsorientierte Basis für Wachstum und Erfolg.

Saso Nikolov