Dienstag, 13. Dezember 2011

Wirtschaftskrise ganz leicht erklärt und warum es nie eine Lösung mit diesem System geben kann

Wenn man sich diese Diskussionen anhört und die Massnahmen und die vielen Empfehlungen fragt man sich bloss, warum denn alles so kompliziert ist?

Warum reden alle über die Bekämpfung der Krankheit, statt über die Krankheit selbst.
Es werden Kopfschmerztabletten vergeben, ohne sich um das warum zu kümmern, so dass diese wenn möglich nicht wieder kehren können.

Warum haben bloss alle soviel Schulden?

Bei dieser banalen Betrachtung nehmen wir den europäischen Raum.
Die Länder brauchen Geld zum arbeiten. Sie drucken ihr benötigtes Geld. Nun passt ja eine übergeordnete Bank auf und druckt dieses Geld. Diese Geld wird dem Land geliehen. (Kennt man ja nicht anders als normal Mensch). Witzigerweise werden für die Geldleihe Zinsen verlangt. Und das ist genau der Trick, der nicht funktionieren kann.

Jahr 1 - Schulden aufnehmen.

Wohl gemerkt sind das eigentlich keine Schulden, sondern der Gegenwert, den das Land wert ist. Früher war das einfacher. Da man direkt den Gegenwert in Gold haben musste. Doch das macht Finanztricks unmöglich, deswegen wurde das auch abgeschafft, sonst fallen diese Finanztricks zu schnell auf.

Bank
100E
Land 1
50E
Land 2
50E
  1. Bank produziert 100 Einheiten
  2. Land 1 erhält 50 E
  3. Land 2 erhält 50 E
Die Bank möchte 10% Zinsen. (damit ist es einfacher zu rechnen)

Jahr 2 - Schulden und Zinsen zurück zahlen

Wenn nun alle Länder perfekt haushalten. Haben alle Länder am Ende des Jahres das ganze Geld in seine Wirtschaft gespült und diese hat damit gearbeitet und das Land hat wieder das ganze Geld beisammen.

Bank
Gegeben: 100E
Zinsen: 10E
Es fehlen
Zinsen: 10E
Land 1
Hat: 50E
Land 2
Hat: 50E
  1. Bank verlangt 100E Kredit
  2. Bank verlangt 10E Zinsen
  3. Land 1 hat 50E
  4. Land 2 hat 50E
  5. 10E Zinsen wurden ja nie hergestellt!
Die Bank möchte 10% Zinsen. Doch diese Einheiten wurden ja nie produziert. Also nimmt das Land wieder Schulden auf, um die Zinsen zahlen zu können. Somit werden die Länder ewig Schulden machen müssen, denn es ist unmöglich dass alle das Geld zurück und die Zinsen zahlen können. Das Geld gibt es NICHT! Das ist wichtig zu verstehen.

Wenn mehrere Länder in einem Topf mitspielen (ähnlich wie in der EU), dann handeln diese Länder miteinander. Dadurch werden Waren und Gelder hin und her geschickt. Sollte bei einem Land mehr Geld hin als zurück fliessen hat er am Ende auch das Geld die Zinsen zu zahlen. Das Verliererland, wird es nicht schaffen und muss wieder Geld aufnehmen. Was die Gewinner wohl nicht sehen, dass dieses System irgendwann zusammen brechen muss. Und zwar dann, wenn es mehr Schulden an Zinsen gibt, als verfügbares und zusätzlich aufnehmbares Geld. Wenn also die Zinsen höher sind, als ein Land (Geld hat + möglicher neuer Kredit).

Klar. Woher sollen diese Gelder auch kommen? Sie wurden ja nie produziert ;-) Genial einfach!


Ewige Schuldensklaven

Deswegen sollen alle Schulden machen. Denn dann tanzt man nach der Pfeife der Geldgeber. Gerade bei den Ländern ist es aber so, dass die Geldgeber man selbst ist. Man verleiht sich selbst Geld und fällt in ein nie endendes Loch! Taschenspielertrick.

Geld kontrolliert die Welt, weil diese die Schulden zahlen muss. Und so halt auch zu Zugeständnissen bereit ist. Besonders schön sind Schuldenerlasse! Aus Luft Gewinn machen und dann davon noch einen Teil verschenken. Herrlich!

Welchen Nutzen ziehen wir durch diese Erkenntnis?

Der Markt dafür ist klar im Gamesbereich. Erstelle dein Spiel so, dass das Geld aus dem System gezogen wird, bzw es immer weniger Geld gibt, als dass es geben sollte. So kannst du die Spieler immer wieder zu kasse bitten!

Samstag, 10. Dezember 2011

Das Wahre Böse gibt es nicht

Mich beschäftigte die Frage nach den bösen Taten.
Gibt es das richtig Böse? Sind alle bösen Taten böse?
Erschreckenderweise kam ich zu dem Schluss, dass es das wahre Böse wohl nicht gibt. Das bereitet mir Sorge. Denn damit wären ja böse Taten zu rechtfertigen. Oder mindestens auch nicht ganz zu vermeiden. Immer mit der Ausrede, dass selbst die schlechte Tat Gutes hat.

Umso mehr hoffe ich das jemand den Fehler in meiner Schlussfolgerung, in meinen Gedanken entdeckt, so dass ich wieder Hoffnung haben kann. Denn ich war immer gewillt gerecht zu handeln.

Hier meine Gründe für meinen Schluss.

Ein Haufen böser Leute tun böses im Zusammenschluss.
Dies tun Sie gemeinsam. Sie schützen sich einander. Sie helfen sich einander. Natürlich nur um das Böse zu tun. Doch sind deren Taten zumindest gegeneinander als gut zu bezeichnen.
Selbst wenn nur ein einzelner Böser, seine Taten vollbringt, dann tut er im mindestens, sich selbst gut. Selbst wenn er sich schlecht fühlen sollte. Die Tat oder irgendwas darin gibt im das so dringende zu stillende Gefühl. Es tut im also in einer Art gut. Er tut es für sich. Wie kann eine Tat die auch gutes bringt, nur aus Böse bestehen. Mindestens ein ganz kleines Stück tat sie Gutes.

Natürlich renn ich nun nicht los und tue fortan nur noch Böses. Doch wäre mir wohler, wenn jemand weisers als ich mir helfen könnte. Also nur zu und zeigt mir das Gegenteil.

Saso Nikolov

Dienstag, 16. August 2011

Virtuelle Tabellen vereinfachen Datenbank basierte Entwicklung

Den Quellcode kann man schnell anpassen und auch auf den Systemen aktualisieren. Eine Änderung am Datenbankdesign ist meist nur schwer zu machen. Oft auch nicht ganz leicht für den Kunden, der eine Webapplikation von einem Freelancer erworben hat, durch zuführen. Mag sein, dass hier der eine oder andere eine gute Geschäftsphilosophie sieht, die dauerhaft Geld bringt, dennoch gibt es auch andere die die Qualität der Software ansich mehr schätzt und durch zufriedene Kunden mit wenig Support-Aufwand sich einen Namen macht.

Wie man Änderungen am Datenbank-Design update sicherer machen kann und auch Aktualisierungen störungsfrei gestalten könnte, bei der eine oder gar mehrer Datenbank-Anpassungen vorgenommen wurden, habe ich in einem anderen Post beschrieben. Hier geht es darum, dass man Tabellen und die dazugehörigen Datensätz mit dem Programmieren anlegen, bzw nutzt, ohne gezielt nochmal eine Tabelle in der Datenbank anzulegen.

Diese hier vorgetragenen Lösungen sind zum Teil durch existierende Frameworks bereits abgedeckt, doch hier geht es um Effizienz und zwar im Betrieb der Software. Da ist es manchmal besser bestimmte Operationen selber zu erstellen und damit den grossen Overhead und viele nicht genutzte Funktionen zu vermeiden.
Einfaches Programmieren und effizientes Programmcode-Design soll die Software für ewig lauffähig halten.
Manche erinnern sich sicherlich noch an die Anfänge von PHP und die dauernden Umstellungen am Klassensystem und anderem, welches es notwendig machte unzählige Zeilen Code nochmal durch zu arbeiten und teils alles neu aufzusetzen. Objektorientierter Ansatz ist gut, doch muss man immer das Verhältnis waren und oft sind Objekte für einige Lösungen überdimensioniert.

Idee
Benötigte Datenbank Felder und Tabellen werden während des Programmierens einfach eingesetzt und entsprechend zur Laufzeit angelegt. Die virtuellen Tabellen, werden in in einer Tabelle gespeichert, deren Felder werden ebenso in einer Tabelle gespeichert. Die Daten zu den Felder werden in einer gesonderten Tabelle gespeichert. Damit entfallen zukünftige Datenbank-Anpassungen für einfache Tabellen, da diese erst auf Zuruf angelegt werden und diese gekapselt per Code. Oft werden Tabellen nur als einfacher Datenspeicher, ohne spätere komplexe Auswertungen, genutzt. Genau da ist der Nutzen der virtuellen Tabellen angesiedelt.
Vorgehen
Wir erstellen 3 Tabellen und die notwendigen Funktionen, um diese später für den Zugriff zu nutzen.
Das Erstellen dieser 3 Tabellen kann mittels des zuvor schon beschrieben Verfahren (älterer Blogpost) eingebunden und somit merkt der Betreiber nicht mal die Erweiterung ansich.

Umsetzung
Benötigte Tabellen
- virdatensatz (id, start, name)
- virtabellen (id, userid, start)
- virvar (id, userid, start, name, vid, art, inhalt, reihe, sprache)



Funktionsübersicht der virtuellen Tabellen
FunktionsnameParameterErklärung
virtabelle_anlegennameLegt eine virtuelle Tabelle an
virtabelle_holentabelle, anlegen=0Liefert die Referenz auf eine Tabelle
virdatensatz_anlegentabelle, userid, tabelleanlegen=1Legt einen Datensatz in einer
virtuellen Tabelle an. Legt diese
ggf. an
virdatensatz_holentabelle, feld=““, datenid=0, counted=0,
sql_option=““, wherefelder=array(),
limit_start=0, limit_ende=0
Liefert die Datensätze einer
virtuellen Tabelle
virdatensatz_komplett_aenderntabelle, userid, virdatensatzid, werteÄndert einen kompletten Daten-
satz mit den übergebenen Werten als Attribute
virdatensatz_komplett_anlegentabelle, userid, werte, tabellenanlegen=1Legt einen Datensatz komplett
mit Attribute an und erstellt ggf.
die virtuelle Tabelle
virdatensatz_loeschentabelle, catidLöscht einen virtuellen Datensatz
virvar_registervar, tabelle, id, userid, wert=””, sprache=0Legt eine dynamische Variable
zu einem dynamischen Datensatz
virvar_unregistervar, tabelle, id, readonly=0,
nur_ausgeben=0, sprache=0
Liefert eine dynamische Variable
von einem dynamischen Datensatz
Anmerkung: Bei Parameter mit einer Wertzuweisung handelt es sich um optionale Parameter mit
einem Standardwert

Funktionen
function virtabelle_anlegen ($name) {// legt eine virtuelle Tabelle an.
 $tname = addslashes(trim($name));
 if ($tname == "")
  return -1;
 $sql = "insert into virtabellen (start, name) values (".time().", '".$tname."')";
 return virtabelle_holen($name);
}

function virtabelle_holen ($tabelle, $anlegen=0) {
 // prüft ob eine virtuelle tabelle existiert
 // bei anlegen = 1, wird diese Tabelle ggf angelegt
 // gibt die ID zurueck von tabelle eine Zahl ist
 $tabelle = trim($tabelle);
    if (intval($tabelle) > 0)
  return $tabelle;
 if ($tabelle == "")
  return -1;
   global $System;
   $id = intval($System[virtabellen][$tabelle]);
 if ($id < 1):
  $sql = "select id from virtabellen where name = '".addslashes($tabelle)."'";
  list($id) = datensatz_holen($sql);// macht mysql_query und dann mysql_fetch_array
   endif;
 if ($id > 0):
  $System[virtabellen][$tabelle] = $id; // zwischenspeichern, um performance zu gewinnen
  return $id;
 endif;
   if ($anlegen == 1)
  return virtabelle_anlegen($tabelle);
   return 0;
}

function virdatensatz_anlegen ($tabelle, $userid, $tabelleanlegen=1){
 // legt einen Datensatz in die Virtuelle Tabelle
 $userid = intval($userid);
 if (intval($tabelle) > 0):
  $vtid = intval($tabelle);
 else:
  $vtid = virtabelle_holen($tabelle, $tabelleanlegen);
 endif;
 if ($userid < 1 || $vtid < 1 )
  return -1;
 $sql = "insert into virdatensatz (start, userid, siteid, vtid) values (".time().", $userid, 0, $vtid)";
 $ret = sql_query($sql); // ruft mysql_query auf, liefert bei INSERT gleich auch die mysql_insert_id
 if ($ret>0):
  return $ret; // neue id
 else:
  return -2;
 endif;
}

function virdatensatz_holen ($tabelle, $feld="", $datenid=0, $counted=0, $sql_option="", $wherefelder=array(), $limit_start=0, $limit_ende=0)
{
 // holt die datensätze samt felder und gibt einen hash zurück, einer virtabelle
 // wenn feld angegeben ist kann man nur bestimmte felder holen, mehrere Felder in einem array
 // wenn datenid > 0 , wird nur dieser Datensatz mit der datenid geholt
 // wenn datenid ein ARRAY ist dann wird die 0 wie eine id betrachtet also id = 0 etc.
 // if counted > 0 , dann wird der array als normaler und nicht als hash zurück gegeben, also wird die vid als id in den hashbestandteil eingesetz, wie normales datensatz_array_holen // beste einstellung
 // wherefelder ist ein array( array("name"=>$name, "inhalt"=>$inhalt)), also hash in vektor
 $vtid = virtabelle_holen($tabelle);
 if ($vtid < 1)
  return array();
 $sql = "select b.name, b.inhalt, b.vid from virvar b ";
 $sql .= " where b.siteid = 0 and b.art = $vtid ";
 $sqlfelder = "";
 // die gewünschten select felder
 $feld = array_bauen($feld); // stellt sicher, dass das erg ein Array ist
 $feld_anzahl = count($feld);
 if ($feld_anzahl > 0):
  $s = "";
  for ($a=0;$a<$feld_anzahl;$a++):
   $feld[$a] = trim($feld[$a]);
   if ($feld[$a] != "")
    $s .= " b.name = '" . $feld[$a] . "' or";
  endfor;
  if ($s != ""):
   $sqlfelder = " and (" . substr($s, 0, -3) . ")";
  endif;
 endif;
 if (intval($datenid) > 0 || (is_array($datenid) && count($datenid) > 0)):
  // vtid ist hier nur sicherheit, dass auch die richtige virTabelle ausgewählt wurde, genauso auch die siteid
  $datenid = array_bauen($datenid); // prüft ob array ist, sonst macht er eines draus
  $sql .= " and ( ";
  for ($a=0;$a<count($datenid);$a++):
   $sql .= " b.vid = " . intval($datenid[$a]) . " or ";
  endfor;
  $sql = substr($sql, 0, -4) . " ) ";
  $sql .= $sqlfelder;
 else:
  $wherefelder = array_bauen($wherefelder);
  $anzahl = count($wherefelder);
  if ($anzahl > 0):
   $sql .= " and ( ";
   for ($a=0;$a<$anzahl;$a++):
    $sql .= "(b.name = '" . addslashes($wherefelder[$a][name]) . "' and b.inhalt = '" . addslashes($wherefelder[$a][inhalt]) . "')";
    $sql .= " or ";
   endfor;
   $sql = substr($sql, 0, -4) . " ) ";
  endif;
        $sql .= $sqlfelder;
       
  $sql .= " " . $sql_option; // suchanweisung bsp: and ((b.name = 'spalte1' and b.inhalt = 'inhalt1') or (b.name='spalte2' and b.inhalt='inhalt2') )
  //$sql .= " , virdatensatz a where a.vtid = $vtid and b.vid = a.id and a.siteid = 0 and b.siteid = 0";

  // nun haben wir die select anweisung um die virdatensätze zu erhalten, jetzt muessen wir noch die sqlfelder holen
  if ($anzahl > 0):
   // wherefelder vorhanden und muessen zuerst ausgewertet werden
   $daten = datensatz_holen_array($sql); // macht mysql_query und mysql_fetch_array
   $anzahl = count($daten);
   $datenids = array();
   if ($anzahl > 0):
    for ($a=0;$a<$anzahl;$a++):
     $datenids[] = $daten[$a][vid];
    endfor;
   endif;
   if (count($datenids)>0):
    return virdatensatz_holen($tabelle, $feld, $datenids, $counted, $sql_option);
      else:
    return array();
   endif;
  endif;
 endif;
 $daten = datensatz_holen_array($sql); // mysql_query und mysql_fetch_array
 $anzahl = count($daten);
 $datensatz = array();
 if ($anzahl > 0):
  for ($a=0;$a<$anzahl;$a++):
   $key = $daten[$a][vid];
   $key2 = $daten[$a][name];
   $datensatz[$key][$key2] = $daten[$a][inhalt];
   $datensatz[$key][id] = $key;
  endfor;
  if ($counted > 0):
   $daten = array();
   while (list($key, $wert) = each ($datensatz)){
    $daten[] = $wert;
   }
  endif;
 endif;
 if ($counted > 0):
  return $daten;
 else:
  return $datensatz;
 endif;
}
function virdatensatz_komplett_aendern ($tabelle, $userid, $virdatensatzid, $werte)
{
 // aendert die übergebenen Werte eines virdatensatzes
 if (intval($tabelle) > 0):
  $vtid = intval($tabelle);
 else:
  $vtid = virtabelle_holen($tabelle);
 endif;
 if ($userid < 1 || $vtid < 1 )
  return -1;
 $werte = array_bauen($werte); // prüft ob wert ein array ist, sonst macht es eines
 for ($a=0;$a<count($werte);$a++):
  function_aufrufen("virvar_register", $werte[$a][name], $vtid, $virdatensatzid, $userid, $werte[$a][inhalt]);
 endfor;
 return 1;
}

function virdatensatz_komplett_anlegen ($tabelle, $userid, $werte, $tabelleanlegen=1) {
 // legt einen virdatensatz komplett mit seinen Feldern an.
 // werte repräsentiert die einzelenen Spalten der Tabelle
 // werte muessen in einem spezielle array vorliegen
 // werte[][name] = name der var (spalte); werte[][inhalt] = inhalt der var (inhalt)
 $vtid = virtabelle_holen($tabelle, $tabelleanlegen);
 if ($vtid < 1 || $userid < 1)
  return -1;
 $virdatensatzid = virdatensatz_anlegen($vtid, $userid);
 if ($virdatensatzid > 0):
  virdatensatz_komplett_aendern($tabelle, $userid, $virdatensatzid, $werte);
 endif;
 return $virdatensatzid;
}


function virdatensatz_loeschen ($tabelle, $catid){
 // löscht einen Datensatz einer virtuellen tabelle
 // und löscht auch alle virvar dazu
 $catid = intval($catid);
 if (intval($tabelle) > 0):
  $vtid = intval($tabelle);
 else:
  $vtid = virtabelle_holen($tabelle);
 endif;
 if ($catid < 1 || $vtid < 1)
  return -1;
   $sql = "delete from virdatensatz where vtid = $vtid and id = $catid and siteid = 0";
 if (sql_query($sql) && sql_affected_rows()>0): // mysql_query && mysql_affected_row
  virvar_all_delete($vtid, $catid);
  return 1;
 endif;
 return 0;
}

function virvar_register ($var, $tabelle, $id, $userid, $wert = "", $sprache=0)
{
       // legt eine var an, egal ob array oder string
       // fuer die tabelle, wobei die id der identifier für den vid ist , also der bezieher zur anderen tabelle
       // wenn wert nicht leer , dann wird der wert aus der variable wert genommen
 $var = trim($var);
 $tabelle = trim($tabelle);
 $id = intval($id);
 $sprache = intval($sprache);
 $userid = intval($userid);
 if ($var == "" || $id == 0 || $userid == 0 || $tabelle == "")
  return 0;
       $art = virtabelle_holen($tabelle, 1);
 if ($art == 0)
        return 0;
       virvar_delete($var, $tabelle, $id, 1);
       $var = addslashes($var);
 $wert = array_bauen($wert);
 for ($a=0;$a<count($wert);$a++):
  $werte = addslashes($wert[$a]);
  if ($werte != ""):
   $sql = "insert into virvar";
   $sql .= " (vid, name, inhalt, start, userid, art, siteid, reihe, sprache) values";
   $sql .= " ($id, '$var', '$werte', ".time().", $userid, $art, ";
   $sql .=  "0, $a, $sprache)";
  sql_query($sql);
  endif;
 endfor;
 return 1;
}

function virvar_delete ($var, $tabelle, $id, $dont_unset=0, $sprache=0)
{
        // loescht eine var
        $var = trim ($var);
        $tabelle = trim($tabelle);
        $id = intval($id);
  $sprache = intval($sprache);
        if ($var == "" || $id == 0 || $tabelle == "")
                return 0;
        $var = addslashes($var);
        $art = virtabelle_holen($tabelle, 1);
        if ($art == 0)
                return 0;
        $sql = "delete from virvar";
  $sql .= " where name = '$var' and vid = $id and art = $art";
  $sql .= " and sprache = $sprache and siteid = 0";
       sql_query($sql);
        if ($dont_unset==0):
                global $$var;
                unset ($$var);
        endif;
}
function virvar_unregister($var, $tabelle, $id, $readonly=0, $nur_ausgeben=0, $sprache=0)
{
 // readonly = 1 ; loescht die Variable aus der var - tabelle
 // readonly = 0 ; erhaehlt die Variable in der var - tabelle
 // loecht bei nicht vorhanden, die variable
 // tabelle darf nur den anfang enthalten, also BSP: content, anstatt contentvar !!
 $var = trim($var);
 $tabelle = trim($tabelle);
 $id = intval($id);
 if ($var=="" || $id == 0 || $tabelle == "")
     return "";
 $var = addslashes($var);
 $art = virtabelle_holen($tabelle, 1);
 if ($art == 0)
     return 0;
 $sprache = intval($sprache);
 $sql = "select inhalt from virvar";
 $sql .= " where name = '$var' and vid = $id and art = $art";
 $sql .= " and sprache = $sprache and (";
 $sql .= "siteid = 0";
 $fremde_hosts = $System->holeHosts ();
 for ($a=0;$a<count($fremde_hosts);$a++):
  $sql .= " OR siteid = " . $fremde_hosts[$a];
 endfor;
 $sql .= ")";
 $sql .= " order by reihe";
 $virvar = datensatz_holen_array($sql);
 $anzahl = count($virvar);
 if ($nur_ausgeben == 0)
     global $$var;
 switch ($anzahl) {
  case 0:
      if ($nur_ausgeben == 1):
          return "";
      else:
          return 0;
      endif;
      break;
  case 1:
      // variable sol nach dem lesen geloescht werden
      if ($readonly == 0)
             virvar_delete($var, $tabelle, $id, 1, $sprache);
      $$var = stripslashes($virvar[0][inhalt]);
      if ($nur_ausgeben == 0):
             return 1;
      else:
          return $$var;
      endif;
      break;
  default:
      $temp = array();
   for ($a=0;$a<$anzahl;$a++):
    $temp[] = stripslashes($virvar[$a][inhalt]);
   endfor;
      if ($readonly == 0)
             virvar_delete($var, $tabelle, $id, 1, $sprache);
      if ($nur_ausgeben == 0):
             $$var = $temp;
             return 1;
      else:
             return $temp;
      endif;
  }// ende switch
}
// hilfsfunktion
function array_bauen ($wert, $bereinigt=1)
{
 // wenn bereinigt = 1, wird nur nicht leere Werte übertragen
 // konstruiert einen Array oder gibt den Array zurück
 if (is_array($wert))
  return $wert;
 $werte = array();
 if (trim($wert) != ""):
  $werte[] = $wert;
 elseif ($bereinigt == 0):
     $werte[] = $wert;
    endif;
 return $werte;
}
Code
Um eine Variable zu belegen
$virdatensatzid = virdatensatz_anlegen ("meinVirTabelle", $userid);
virvar_register ("meineVar", "meineVirTabelle", $virdatensatzid, $userid, "meinWert");
echo virvar_unregister", "meineVar", "meineVirTabelle", $virdatensatzid, 1, 1);
Fazit
Sollen mit den Daten komplexe Datenbank-Operationen gemacht werden, ist die Nutzung von virtuellen Tabellen oft zu aufwendig und komplex und widerspricht dann dem Ansatz der Effizienz. Für reine Sicherung von Daten, welche nur ausgelesen und ab und an geändert werden sollen, ist der Nutzen sehr hoch. Gerade bei der Entwicklung. Wartbarkeit, bzw Operationen direkt über die Datenbank an den Daten sind sehr aufwendig und mühselig. Aber dafür könnte man auch ein Skript schreiben.

Saso Nikolov

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

Freitag, 27. Mai 2011

Unendlich viele Nachkommastellen anzeigen

Idee
Ich wollte eine Nachkommastelle prüfen und stellte fest, dass die meisten Rechner soweit nach dem Komma nicht mehr anzeigen. Verständlich, jedoch gibt es sehr seltene Fälle wo man dies braucht. Klar wieder eine der nice to have und nie genutzte Funktion, aber ich wollte es euch nicht vorenthalten, falls jemand schnell eine Funktion braucht und nur Copy&Paste machen will. Die Gesamtgroesse ansich ist auf 1000 Zeichen vorbelegt und kann mittels Parameter angepasst werden. Die errechnete Zahl ist ein Text, klar, sonst könnte man per Boardmittel das Ergebnis errechnen.

Umsetzung
Die Funktion ist in JavaScript geschrieben. Um den Code etwas zu erleichtern, habe ich die Formel für die Umwandlung der Zahl in eine Float als eigene Funktion ausgegliedert. Die Umwandlung in float ist hilfreich, wenn die Werte über ein Formular oder eine externe Datenquelle kommen und man nicht immer prüfen will, ob es im richtigen Format vorliegt. Also wird einfach alles konvertiert und wird immer eine float Zahl.
Angehängt ist auch eine rekursive Variante, aber die sollte man nicht nutzen, denn der Overhead kann den Rechner sehr belasten. Ich wollte die nicht weglassen, falls man einfach sehen will, dass Rekursion nicht immer die beste Wahl ist, vor allem nicht auf Performance ausgelegte Strukturen. Mit Sicherheit kann man den Code noch optimieren, aber so sieht man den Weg viel einfacher. Feel free to abuse it.

Code
// Hilfsfunktion floatval

function floatval(text, maxwert, minwert) {
    var zahl = parseFloat(text);
    if (isNaN(zahl))
        zahl = 0.0;
    if (typeof(maxwert) != "undefined" && maxwert < zahl)
        zahl = maxwert;
    if (typeof(minwert) != "undefined" && minwert > zahl)
        zahl = minwert;
    return zahl;
}

// Die eigentliche Funktion

function errechneZahl(zahla,zahlb,maxschritte) {
var t = "";
zahla = floatval(zahla);
zahlb = floatval(zahlb);
var ok = true;
var schonkomma = false;
var za = zahla;
var zb = zahlb;
var periode = new Array();
if (!maxschritte)
maxschritte = 1000;
for (var a=0;a<maxschritte;a++) {
if (za < zb){
if (t == "" || schonkomma)
t += '0';
if (!schonkomma) {
t += '.';
schonkomma = true;
} else
periode.push({"zahl":0,"wert":0});
za *= 10;
} else {
var d = Math.floor(za/zb);
t += d;
var dd = za - (d*zb);
if (dd == 0)
break;
if (schonkomma) {
var pwert = "";
for (var b=0;b<periode.length;b++){
if (periode[b].zahl == dd || pwert != "") {
pwert += ""+periode[b].wert;
}
}
if (pwert != "") {
t += " Periode "+pwert;
break;
}
periode.push({"zahl":dd,"wert":d});
}
za = dd;
if (schonkomma)
za *= 10;
}
}
return t;
}

// die rekursive, nicht empfohlene Version
/*
function errechneZahlRekursiv(zahla, zahlb, ergtext, schonkomma) {

var t = "";
zahla = floatval(zahla);
zahlb = floatval(zahlb);
if (ergtext)
t = ergtext;
if (zahla == zahlb) {
t += "1";
return t;
}
if (t.length > 1000)
return t;
if (zahla < zahlb) {
if (t == "" || schonkomma)
t += '0';
if (!schonkomma) {
t += '.';
schonkomma = true;
}
zahla *= 10;
t = errechneZahl(zahla, zahlb, t, schonkomma);
} else {
var d = Math.floor(zahla/zahlb);
t += d;
var dd = zahla - (d*zahlb);
if (dd == 0)
return t;
if (schonkomma)
dd *= 10;
t = errechneZahl(dd, zahlb, t, schonkomma);
}
return t;
}
*/

Fazit
Interessant um Werte schnell zu überprüfen. Rechnen kann man damit spontan nicht, aber man könnte auch Funktionen schaffen, welche beliebig große Zahlen addieren und dergleichen. Hier ist lediglich eine schnelle, einfache Funktion, welche viele Nachkommastellen anzeigen kann. Viel Spaß damit.

Saso Nikolov

Samstag, 14. Mai 2011

VoIP - Ist Voice over IP eine wirkliche Alternative?


Auf dem VoIP Markt gibt es im Grunde nur wenige Möglichkeiten. Die zwei bekanntesten sind Skype und SIP Provider. Skype hat den Vorteil als guter Kommunikationsmanager vieles in einer Software zu vereinen und damit vieles einfach zu machen. Und dies kostenfrei, bzw mit geringen Einstiegskosten. Da sehr viele Skype nutzen, sind die Chancen gross kostenlose Telefonate zu tätigen. Zudem gibts neben Chat auch Gruppenchat, Videokonferenz, Screensharing und Fileexchange. Aber hier fehlen die Verbindungen zu den anderen Elementen wie Email und Dateienmanagement. Allerdings sind die Kosten für Anrufe höher als bei manchen SIP Anbieter. Die eingesetzte Technik ist aber bekanntermassen ziemlich gut und hat keine Probleme mit Firewalleinstellungen.
Irgendwie scheint das SIP nicht ideal durchdacht, da es 2 Ports braucht, die nicht immer freigeschaltet sind. Somit ist es oft nicht immer möglich spontan sein SIP Telefon, ob Software oder Hardware überall zum Einsatz zu bringen. Es sind einfach zu viele Komponenten involviert, auf welche man keinen direkten Einfluss hat. So kann die Verbindung zum Router sehr gut sein und auch zum Provider, doch wenn der SIP Server woanders liegt, ist hier nicht immer gewährleistet, dass es ein QoS gibt. Vorallem wenn mehrere Carrier involviert sind.


Fazit
VoIP ist spitze für firmeninterne Kommunikation. Dort kann man die Netzwerkelemente abstimmen und den QoS einrichten. Hat man auch entsprechende Server ist die Konnektivität über verschiedene Ebenen hinweg vereinfacht, jedoch auch entsprechend teuer in den Anschaffungskosten. Für kleine Einheiten, die eine günstige Telefonergänzung zu ihrem online Office suchen, ist die Skype Lösung besser. Da kaum Startkosten und auch mit Skypein eine externe Rufnummer zur Verfügung gestellt wird. Microsoft hat Skype erworben, so bleibt zu hoffen, dass die Einbindung zum Office Paket schnell geschiet. Dann könnte das easy Full-Web-Office am Strand möglich werden. Vorallem dank der immer besseren Smartphones.

Donnerstag, 28. April 2011

Webmail client. Outsorcing der Mails

Emails unkompliziert von überall bearbeiten
Das mobile Office ist ja schon lange Realität. Die meiste Arbeit ist Emails, Dateien bearbeiten und Dateien versenden. Nun habe ich lange nach einer Möglichkeit gesucht, den eigenen Mailserver zu ersetzen und doch so optimal wie möglich zu arbeiten. Klar die Grossen wie Gmail und Hotmail sind da auf jeden Fall zu berücksichtigen. Also habe ich nun mal alle getestet und meine Erfahrung hier aufgeschrieben. Als Alternative habe ich Zimbra genommen. Ziel war es nicht nur einen guten Mailclient mit Kalender und dergleichen zu haben, sondern auch eine eingebaute Dateiverwaltung. Denn nur so kann man am Strand beim Cocktail alle Arbeiten verrichten. Mein grosses Ziel ist es ein wartungsfreies online Office zu haben. Der Email-Baustein ist das wichtigste, da ja der tägliche Helfer.

Was wurde bewertet?
Klar das Emailhandling (versenden, bearbeiten), verschiedene Accounts einbinden auch von anderen Domains, die einem gehören. Also Einbindung von anderen POP und IMAP Konten. Kalenderfunktion, hier auch die Möglichkeit unkompliziert die Emails mit dem Terminen zu verbinden. Dateiverwaltung (anlegen, editieren) und die Möglichkeit diese mit Emails zu verbinden. Also wie die gängigen Bereiche mit einander arbeiten. Auch die Erweiterung ist wichtig, so dass man evtl eigene benötigte Operationen machen kann. Natürlich sollte alles super einfach auf bestehende mobile Telefone passen.

Hotmail
Ganz klar der Profi unter den Weboffice angeboten. Echt gut. Dazu offeriert MS auch gleich noch ein üppigen Speicherplatz und die Möglichkeit unter Windows das Laufwerk zu mounten. Geradezu genial ist die Möglichkeit Hotmail via Exchange Zugang einzurichten. Daneben bietet MS nun auch mit Office365 eine super Lösung, zumindest auf Papier. Hier deckt sich das Mailangebot in der Funktionalität mit der von Hotmail. Konnte es noch nicht ausgiebig testen, da schon gleich zu beginn das gleiche Problem mit den Attachments wie bei Hotmail aufkam. Als erstes fällt auf, dass der Dienst etwas langsam ist. Bietet aber vieles. Vorallem ist es auch möglich verschiedene Soziale Dienste zu integrieren, diese werden auch auf den Chatbereich ausgedehnt. So kann man mit allen Kontakten auf jeder Applikation chatte, egal in welchem sozialen Netzwerk der Konversationspartner gerade ist. Es wirkt aber etwas unordentlich. Der Email Bereich ist in gewohnter Form und man findet sich schnell zurecht. Gut ist das die Liste mit den Emails offen bleibt, während man den Inhalt lesen kann. Wie im Outlook. Es fehlt die Möglichkeit eine Email mit einem Todo oder Kalendereintrag zu verbinden. Gerade dies ist oft für den BusinessAlltag nötig. So kann man wunderbar vom Termin auch gleich an die zugehörigen Emails gelangen. Leider haben dies die Leute von Microsoft nicht realisiert. Die Online-Kontakte werden angezeigt und das auch Dienste übergreifend. Es gibt einfach zu bedienende Aufräumaktionen direkt aus der Emailansicht heraus. Man kann die Emails mit nur Flaggen und nicht mit verschiedenen Tags versehen. Cool ist der Standard Filter, so dass man alle Emails mit Bilder oder Office Dokumente listen kann. Es gibt eine Suche, welche auch verfeinert werden kann. Beim Email verfassen, kann man auch Texte formatieren und einen Spellcheck gibts auch. Sehr gut ist die Möglichkeit direkt beim Verfassen eines Textes Suchanfragen zu starten und die Inhalte aus dem Internet direkt zu einbinden. Die Suche kann auch nach den verschiedenen Medien gefiltert werden. Mit einem Klick auf den Suchtreffer, landet die URL samt Beschreibung in der Email. Genauso auch Bilder. Diese kann man dann auch editieren. Man kann Dateien per Upload hinzufügen, jedoch nicht auch die hinterlegten Dateien zugreifen. Damit hat man das Problem, dass man erst die online Dokumente herunterladen muss, um diese wieder aufzuspielen. Das ist sehr schlecht gelöst. Da merkt man deutlich, dass die Produkte unabhängig voneinander sind. Google hat das Gleiche Problem. Man kann externe Accounts einbinden, sogar ganze Domains umstellen. So kann man hotmail als seinen Mailserver nutzen. Dazu muss man Zugriff auf den DNS Eintrag seiner Domain haben. Das Abholen von exterenen Accounts gehört bei allen zum Standard. Der Eingebaute Messenger spricht mit allen Kontakten, egal auf welcher Plattform der Kontakt sich befindet.

Der Kalender verfügt über verschiedene Ansichten und man kann auch weitere Kalender erstellen. Ebenso ist das Einbinden von externen Kalender kein Problem. Hier ist auch die Todo-Liste. Das Anlegen eines Eintrags geht im Schnellmodus und im erweiterten Zustand, so dass man gleich mehr Optionen einstellen kann. Der Kalender kann auch für andere frei gegeben werden, für sowohl andere Hotmail Nutzer als auch komplett öffentlich. Da kann man sehr genau die Freigaben einstellen. Natürlich kann man andere Teilnehmer wie gewohnt zu einen Termin einladen.

Die Weboffice Applikation ist echt gut. Allerdings ist die Integration nicht optimal gelungen. Man klickt auf den Office Bereich und erhält dort wiederum eine Startseite. Das ist erstmal nicht so weit störend, doch wenn man öfter mit den Dokumenten arbeitet und zwischen den Email hin und her klicken muss, ist das echt nerven aufreibend, zumal die Geschwindigkeit sehr zu wünschen übrig lässt. Hier hat man auch zugriff auf SkyDrive, den online Speicherplatz, der auch 25GB kostenfreien Platz beinhaltet. Das ist echt enorm. Leider auch hier wieder erst sichtbar über eine Startseite. Für das tägliche Arbeiten wird das einfach zu viel. Man kann Dateien mit anderen teilen und freigeben. Genauso kann man mittels Email, direkt Dateien auf den Skydrive einspielen. Diese Option ist sehr geschätzt und meine Hauptfunktion bei Alfresco. Eben mal schnell die Email mit der Rechnung auch gleich in den Rechnungsordner und zur Buchhaltung senden. Und das nur mittels einem Klick. Die Verknüpfung mit den verschiedenen Tools ist leider noch nicht abgeschlossen und bringt auch schon wegen der Wartezeit Frust. Natürlich ist hier auch das Problem für den einen oder anderen, dass die Daten bei jemanden anderes liegen der auch schon aufgrund der AGB mitlesen darf. Eine konkrete Anfrage, ob denn nun MS auch die Texte und Daten liest, wird immer nur lapidar beantwortet. Man windet sich und auch nach mehrmaligen, direkten Fragen, ob ein Weboffice Dokument nun gelesen wird oder nicht, wir man nur hin und her gesendet. Also denke ich ganz klar, dass die Daten nicht geheim bleiben, wenn es MS nicht will. Wenn man keine grosse Nummer denke ich, ist das Risiko gering.

Gmail
Der ganz grosse unter den Webmailer. Die ersten die einen richtig guten Webmailer kostenlos für alle angeboten haben, der auch noch richtig schnell ist. Die Emails kann man mit verschiedenen Tags belegen und verwaltet so statt in Ordner die Mails in logischen Markierungen. Das Design gewohnt schlicht und vermutlich auch deswegen gut. Man kann sich die verschiedensten Minielemente von den einzelnen Tools wie Kalender und Dokumente im Webmail Bereich einblenden lassen. Direkt eingebunden ist die Todoliste und das Chatfenster. Anders als bei hotmail scheint der Chat aber auf Google beschränkt zu sein. Dazu gibt es die Möglichkeit kleine Tools aus dem Google Labs einzubinden. Die Emails werden in Konversationen zusammen gestellt. So hat man einen sehr guten Überblick über die Email Konversation. Dazu blendet Gmail geschickt unnötige Wiederholungen bei Antworten aus. Vieles geht intuitiv und wirkt durchdacht. Man kann sehr leicht auch von verschiedene Absender aus versenden, was sehr praktisch sein kann, wenn man unterwegs ist und eine Mail mit einem bestimmten Absender versenden will. Dazu muss man nur einmal die besagte Emailadresse bestätigen.

Der Kalender ist super. Man kann Termine mit einem Klick anlegen und Google parst den Text und legt entsprechend den Eintrag an. So kann man auch "20:00 zahnarzt" schreiben und der Termin wird auf 20:00 gelegt. Hier kann man auch kleine Helfer aktivieren und mehrere Kalender anlegen und auch diese entsprechend freigeben oder öffentliche und freigegeben Kalender einbinden. Das Einladen von Teilnehmer an Termine ist ebenso enthalten, wie die Erinnerungsfunktion. Was gut ist, ist die Möglichkeit auch Anhänge zu einem Termin zu hinterlegen. Hier kann man sehen, wie es auch für das Mail gehen sollte. Man kann Dateien hochladen oder direkt aus der Google Text & Tabellen App auswählen. Auch die Erinnerung kann beliebig geschachtelt werden. So kann man mehrmals daran erinnert werden und das auf verschiedene Weisen (Email, Popup im Browser). Zwar ist das die Parade Diszilin von MS Exchange doch in hotmail vermisst, bietet Google die Möglichkeint einen geeigneten Zeitpunkt zu finden, um das Termin planen zu vereinfachen. Leider fehlt die Verbindung zu den Emails. Man kann aber Emails auf die Todoliste setzen und diese kann auch mit Datum ausgestattet werden. Auch bei Google kann man weitere externe Accounts einbinden, allerdings kann man nicht weitere Gmail accounts verbinden. Das ist ärgerlich wenn man zum Beispiel neben dem normalen Gmailaccount auch noch einen Corporate Account hat. So kann man auch hier den kompletten Mailverkehr für seine Domain über Google laufen lassen.

Google bietet mit Text & Tabellen ein sehr interessantes Werkzeug, um Dokumente zu verwalten und zu erstellen. Diese Tools sind durchaus brauchbar und nicht zu unterschätzen. Allerdings sind es natürlich keine vollwertigen Desktop-Word Ersatz und Excel Programme, aber für Briefe, auch geschäftsbezogene eignen sich diese Dienste hervorragend. Die Übersicht der Dateien ist etwas gewöhnungsbedürftig. Aber es gibt eine Vorschau. Man kann Dokumente freigeben und mit anderen gemeinsam zur gleichen Zeit bearbeiten. Super bei Kalkulationen die über Skype besprochen werden. Dabei kann man auch live Diskussionen über ein Dokument erstellen und diese werden auch zum Dokument gespeichert. Das Dokument kann in verschiedenen Versionen gespeichert werden. Auch der Export bietet alles was man braucht. Gerade das Versenden des Dokuments ist hier viel besser als bei hotmail gelöst. So kann man das Dokument auch direkt als PDF Attachment anhängen. Wie bei hotmail kann man auch hier das Dokument online erreichbar machen und die URL an Dritte weiter geben. Leider fehlt auch hier die direkte Verbindung zu Gmail. Man kann jeweils nur ein Dokument per Email versenden. Man kann nicht erkennen wieviel Speicherplatz man hat, jedoch kann man auch wie bei hotmail Dateien hinterlegen. Allerdings ist ein direkte Einbindung als Laufwerk in Windows nicht möglich. Sehr bedauerlich ist es, dass man nicht direkt aus dem Gmail heraus auf die Dateien zugreifen kann. Man muss alle herunter laden und dann wieder aufspielen. Das ist wirklich ein Armutszeugnis, da google sonst so innovativ ist.

Eines muss man aber sagen, die Geschwindigkeit ist sehr gut. So kann man auch in hektischen Situationen die Apps nutzen, ohne gleich nervös auf die Maus zu hämmern. Über das Mitlesen von den Daten muss man bei Google wohl nix schreiben.

Zimbra
Der letzte im Bunde ist Zimbra. Zimbra ist eine komplette Email/Groupware Suite. Ist nicht direkt kostenlos, aber dafür gibt es eine kostenfreie Community Version, die man auf einen Server installieren kann. Das ist auch der grosse Vorteil. Die Daten bleiben auf den eigenen Servern. Server kann man mittlerweile sehr günstig erhalten. Natürlich gibt es auch Anbieter, welche nur einen Emailaccount anbieten.  Die Webmail Oberfläche kann man in verschiedene Designs haben. Man kann eigene Tags anlegen und die Dateien wie gewohnt in Ordner verwalten. Externe Accounts und Domains einbinden und von aussen per Imap oder POP3 Emails abrufen. Auch hier kann ein Minikalender in der Mailansicht einblenden lassen. Zimbra lässt sich leicht mit Addons, sogenannte Zimlets erweitern. Diese kann man aus dem freien Entwickler Portal einbinden oder selbst schreiben. Damit kann man Zimbra wirklich extrem erweitern, da oft sehr nützliche für den Business-Alltag benötigte Elemente bereitgestellt werden. So kann man die Social Welt einbinden und neben Facebook auch seine Twitter-Accounts komfortable verwalten und nutzen. Die Mails können auch im Koversationsmodus wie bei Gmail angezeigt werden. Dabei ist hier eine gelungen Mischung zwischen Gmail und hotmail entstanden. Beim Erstellen von Emails hat man dann endlich eine gelungen Kombination von Dateiverwaltung und Email. Attachment in Form von Uploads oder direkt aus dem Online-Storage von Zimbra sind ganz einfach einzubinden. Hat man auch das Alfresco Zimlet installiert, geht dies sogar vom entfernten Alfresco Server. Man kann ganz einfach auch Emails als Anhang einbinden, oder Kontakte. Ideal auch für Firmen ist der eingebaut Messenger, der allerdings nur die vom Server verwalteten User unterstützt. Nice ist hier, dass die Chats wie Emails auch gespeichert und durchsucht werden können. Mails können als Aufgabe oder Termin erfasst werden. Leider ist es sehr umständlich von verschiedenen Absender aus zu versenden. Das ist ähnlich wie bei Exchange sehr umständlich gelöst. Gerade für Leute die auf Verteilerlisten stehen, mühselig, da diese nicht mit der Verteileremailadresse antworten können.

Der Kalender ist ebenso komfortabel wie der von hotmail oder google. Auch hier kann man Kalender freigeben oder externe einbinden. Man kann Termine mit Anhängen belegen und Teilnehmer hinzufügen. Top ist auch die Planer Funktion, wie bei Gmail, ist hier das finden eines gemeinsamen Termins schnell gemacht, sofern die Teilnehmer ebenfalls auf dem Zimbra-Server ihren Account haben.

Frühere Versionen hatten ebenfalls eine WebOffice Paket, das ist nun nur noch für Text vorhanden. Tabellenkalkulation ist verschwunden. Man kann eigene Dateien hinterlegen und diese beliebig freigeben und somit auch einen Fileserver betreiben. Speicherplatzbegrenzung hängt vom eigenen Server ab. Die Ansicht wird wie bei Email gehalten und man findet sich schnell zurecht. Schön ist auch die Vorschau. Wirklich top ist die Möglichkeit die Dateien in Emails als Anhang zu versenden. So lässt es sich arbeiten. Leider kann man nicht wirklich Dokumente bearbeiten. Dennoch ist der Texteditor gut und für viele Arbeiten ausreichend. Die Dateien können mit den Tags belegt werden, so auch die Termine, Kontakte und Todo-Einträge. Das ermöglicht ein Zusammenfassen der verschiedensten Elemente zu einem gesamtheitlichen Kontext. Wunderbar um schnell Daten zufinden. Einige Zimlets erlauben sogar SMS Versand, Einbindung von VOIP und andere oft genutzte Dienste. Allerdings sind einige nur für bestimmte Länder sinnvoll. Aber es ist ja eine offene Schnittstelle und man kann mit steigender Verbreitung von Zimbra auch auf weitere Zimlets hoffen.
VMware hat Zimbra erworben und bietet passende Versionen an. Es bietet sich auch wirklich ideal dafür an, da ein Zimbra Server, viele Dienste einsetzt. Wenn man die Möglichkeit hat, eine virtuelle Maschine einzusetzen, dann hat man es super leicht zu starten. Aber auch ein frisch aufgesetzter Server kann sehr leicht zum Zimbra Server gemacht werden.

Fazit
Wenn man sich ein Wunschprogramm aus den 3 bauen könnte,
würde ich die WebOffice App von hotmail mit den Google Umwandlungskünsten verbinden und die Verwaltung durch Zimbra machen lassen. Die Social Leistung von hotmail und die Einfachheit von Gmail im Einbinden von weiteren Accounts und Emailversender zusammen fehlen dem Zimbra. Neben dem Vorteil der Datensicherheit spricht vieles für Zimbra. Der Nachteil ist, dass man sich um den Mailserver kümmern muss. Auch wenn man das alles automatisiert, bleibt immer mal was an Arbeit hängen. Dennoch ist die Verknüpfung der Bereich in Zimbra vorbildlich gelöst und es macht richtig Spass mit dem Programm zu arbeiten. Die Erweiterbarkeit spricht für Zimbra, wenn man eine grössere Firma mit spezial Wünschen ist.

Freitag, 22. April 2011

Cookies mittels JavaScript bearbeiten

Cookies
Cookies nennt man die kleinen textuellen Informationen, welche vom Browser auf dem Client gespeichert werden. Der Browser stellt in der Regel sicher, dass diese Cookies pro Domain verwaltet werden und beim Aufruf der Domain immer mitgesendet werden. So kann man komfortable kleine Informationen ablegen und den Client wieder erkennen. Sehr sinnvoll, um zum Beispiel die eingestellte Wunschanzeigesprache der Webseite wieder herzustellen. Im Cookie sind in der Regel Key-Value-Pairs gespeichert und diese können auch mit einem Verfallsdatum versehen werden. Da Cookies in der Regel URL-Pfad abhängig gespeichert werden, kann man auch hier einen Pfad angeben, um die Information auch von Unterseiten auf der Startseite zu erhalten.
Cookies auf SelfHTML

Einfache Cookie Behandlung mittels JavaScript
Mit JavaScript kann man diese Cookies auch setzen und auslesen. Leider ist die Funktionalität nicht so gut, also helfen wir uns mit eigenen Funktionen. Das geht zum Glück sehr leicht. Natürlich kann man alles in eine Klasse packen, doch hier soll die Funktion reichen. Es sind auch nur 2. Die Set-Funktion bekommt
den Keynamen vom Key-Value-Pair, den Wert (also Value), evtl ein Gültigkeitsdatum in Millisekunden und einen optional einen Pfad. Das Lesen ist noch einfacher. Wir geben nur den gewünschten Key an und erhalten einen Value. Wenn Key nicht vorhanden ist, erhalten wir eine leere Zeichenkette.

Cookie Funktionen
function cookie_set(name, wert, miltime, pfad) {
  if (navigator.cookieEnabled == false)
    return false;
  var str = name+"="+wert.replace(/;/g,"")+";";
  if (miltime) {
    var ablauf = new Date();
    ablauf.setTime(miltime);
    str += " expires="+ablauf.toGMTString();
 }
 if (pfad)
   str += ' path='+pfad;
   document.cookie = str;
   return true;
}

function cookie_get(name) {
  if (!document.cookie)
    return "";
  var str = document.cookie;
  if (str == "")
    return "";
  var teile = str.split(";");
  for (var a=0;a<teile.length;a++) {
    var pos = teile[a].indexOf("=");
    if (name == teile[a].substr(0,pos))
      return teile[a].substr(pos+1);
  }
  return "";
}

Beispiel Einsatz
Wir möchten je nachdem wie der User auf unsere Seiten gelangt ist, ihm immer wieder die gleiche Startseite zeigen. Ideal um verschiedene Startseiten zu testen. Die Landingpage im Pfad landingpages/ setzt ein Cookie.
Das Cookie wird dann in der eigentlichen Startdatei im Pfad / ausgelesen.

Landingpage:
var filename = "ostern.html";
cookie_set("landingpage", filename, verfallsdatum.getTime(), teile.join("/")+"/");

Eigentliche Startseite:
if (cookie_get("landingpage")) {
  var bereich = cookie_get("landingpage");
  return document.location.href = "landingpages/"+encodeURIComponent(bereich);  
}

Fazit
Mit diesen Funktionen kann man sehr leicht sein Framework ausbauen und eine simple einfache Cookie-Behandlung nutzen. Ausbaufähig ist eine Cookie-Speicherung von komplexeren Werten. Jedoch sollte man nicht vergessen, dass Cookies nicht unbedingt sehr sicher sind, immer an die Domain mitgesendet werden und eigentlich nur für nicht wichtige Bestandteilen gedacht ist. Ich würde keine komplexen Daten dort sichern, da einige Clients die Cookie Unterstützung deaktivieren.

Saso Nikolov

Mittwoch, 20. April 2011

Ajax oder doch lieber Api-Call

Dank JavaScript ist es möglich selbst auch statische Seiten dynamisch zu gestalten. Dabei bietet Ajax die einfache Möglichkeit Elemente nach zu laden. So kann man die Angangsseiten schlank halten und die benötigten Elemente bei Bedarf nach laden. Ajax hat allerdings ein Sicherheitsschutz, der es schwer macht Cluster oder gar verteilte Seiten zu verbinden. Hier ist das Thema Crosssdomain von Bedeutung.

Crossdomain Problem
Aus Sicherheitsgründen ist es einer Webseite nicht erlaubt per Ajax auf andere Subdomains zugreifen zu können. Wenn jemand eine komplette Domain für sich selbst nutzt scheint es erstmals unnötig, doch wenn man Subdomains nutzt und andere ebenso Subdomains mit der gleichen Domain nutzen, wird diese Sicherheitshürde verständlicher. Eigentlich ist das kein grosses Problem, wenn man nur eigene Inhalte vom eigenen Server herunterladen möchte. Doch was ist, wenn man verschiedene Subdomains einsetzen möchte, um zum Beispiele verschiedene Kunden zu unterscheiden und dennoch allen eine gemeinsame Komponente anbieten möchte? Oder man hat soviel Traffic, dass man verschiedene Server nutzt und diese eventuell für Zugriffe aus verschiedenen Kontinenten verteilt hat. Klar man kann nun wieder ein komplexes System mit Loadbalancer und dergleichen aufbauen, doch unser Ziel ist es ja effizient und einfach zum Ziel zu kommen. Vorallem sollten so wenig externe Komponenten wie möglich zum Einsatz kommen. Interessant ist auch die Möglichkeit SSL für das nachträgliche Nachladen zu nutzen, und so die Zugriffspfade für versteckte Dateien zu verstecken. Ein wildcard-SSL-Zertifikat ist schnell erstellt, doch wenige besitzen einen CA, der in allen Browser implementiert ist. Der lästige Browserhinweis ist kaum einem Kunden zu zumuten. Mehrere Zertifikate zu kaufen, ist auch nicht billig. Also könnte man ein Zertifikat erwerben und darüber die SSL Kommunikation machen. Sagen wir mal für die subdomain www. Nun ist es ärgerlich wenn einer eine andere subdomain eingibt, oder eventuell überhaupt keine Subdomain in die Adressleiste des Browser eintippt. Sollten wir diesen Kunden ignorieren? Nein, klar nicht. Ein Umleitung auf die richtige Subdomain ist möglich, doch was ist, wenn Parameter übergeben werden. Diese müsste man auch immer mit umbiegen. Nicht sehr effizient, aber machbar. Wir könnten aber auch als das so sein lassen, wie es ist und statt einer Ajax-Lösung eine andere Methode nutzen, um dynamisch Inhalte nachzuladen. Und das auch noch ohne an  Sicherheitsschranken zu stossen.

Api-Call
Leider habe ich kein richtiges Wort für diese Methode und wir nannten sie immer intern die Apicall-Methode. Darum will ich auch bei diesem Namen bleiben. Sie ist simple wie genial, erfordert jedoch auch eine gewissen Umstellung der Daten. Die Idee ist, dass per JavaScript dynamisch JavaScript Dateien nachgeladen werden. So wird bei Bedarf einfach ein Skript-Tag eingebunden. Dieses JavaScript Code Stück enthält dann die Daten. Diese müssen nur in einer speziellen Form verfasst sein. Man könnte direkt eine JSON ablegen und danach pollen. Doch einfacher geht es, wenn man einen Funktionsaufruf erstellt und dieser die Daten als Parameter enthält. Man kann da beliebig komplex werden. Allerdings hat diese Methode ein paar gravierende Nachteil gegenüber Ajax. Es fehlen die Informationen zum Netzwerkstatus. Man kann also nicht mit absoluter Sicherheit sagen, ob eine Ressource fehlt. Doch da kann man sich grob behelfen. Mit dieser Methode kann man nun auch von anderen Diensten nur mittels JavaScript Daten einlesen, sofern diese den apicall unterstützen. Das ganze ohne einen Serverdienst, der als Proxy fungiert.

Was brauchen wir dafür?
Nicht viel. Wir sollten zuvor 2 Funktionen erstellen, die das Handling machen und können dann loslegen  Daten einzulesen. Die Daten können von dynamischen oder statische Server stammen. Die Daten müssen allerdings aufbereitet werden. Aber man gewöhnt sich schnell dran und vorallem wenn dynamische Programme diese Dateien erstellen, kann man dies schnell erledigen lassen.
Damit wir etwas Error-Handling betreiben habe ich noch eine dritte Funktion hinzugefügt. Die man aber auch weglassen kann. Damit die Kommunikation zwischen den Funktionen einfach gehalten werden kann und wir nicht extra eine Klasse definieren müssen, legen wir auch eine globale Variable an. Also eine Variable direkt im Skriptbereich. Ein komplexerer Variante, aber immer noch reine Funktionen findet man in der http://directpaylink.com/javascript/basics.js

Die JavaScipt Funktionen
var system = new Object();

function apicall(url, onSuccess, onError, onErrorTimeout, urlid) {
  if (!system)
    system = {};
  if (!system['dscript'])
    system['dscript'] = {'id':1, 'cb':{}, 'urls':{}};  
  var id = system["dscript"]["id"];
  system["dscript"]["cb"][id] = {"url": url, 
"onSuccess": onSuccess, "onError": onError, 
"time": new Date().getTime()};
  var refurl = url;
  if (!urlid)
    urlid = refurl
  system["dscript"]["urls"][urlid] = id; // für die statischen URLs
  var head    = document.getElementsByTagName("head")[0];
  var script  = document.createElement('script');
  script.id   = 'obj_dscript_'+id;
  script.type = 'text/javascript';    
var srcurl = url;
  if (srcurl.indexOf("?")<1)
    srcurl += "?"      
  script.src  = srcurl+"&cbf=RS_APIDispatcher&cbid="+id;
  var d = new Date();
  script.src += '&t='+d.getTime();
  head.appendChild(script);
  var timeout = 5000; // 5 sekunden
  if (onErrorTimeout)
    timeout = onErrorTimeout; 
  var errorcheck = setTimeout("RS_APIErroLoadCheck('"+id+"')", timeout);
  system["dscript"]["cb"][id]['errorcheck'] = errorcheck;    
  system["dscript"]["id"]++;
}   
function RS_APIDispatcher(id, result) {
  if (id == 0 && system["dscript"]["urls"][id])
    id = system["dscript"]["urls"][id];
  if (!document.getElementById('obj_dscript_'+id))
    return false;
  if (system["dscript"]["cb"][id])
    system["dscript"]["cb"][id]['dontkill'] = true;
  if (system["dscript"]["cb"][id] && system["dscript"]["cb"][id].errorcheck)  
    clearTimeout(system["dscript"]["cb"][id].errorcheck);
  var old = document.getElementById('obj_dscript_'+id);
  if (old != null) {
    old.parentNode.removeChild(old);
    delete old;
  }
  var oobj = null;
  if(typeof result == 'function')
    result = result();
  else if(typeof result == 'object') 
    oobj = result; 
  var obj = {"responseText": result, "object": oobj};
// errorload could have killed it already
if (system["dscript"]["cb"][id]) system["dscript"]["cb"][id].onSuccess(obj); 
delete(system["dscript"]["cb"][id]);   
}
function RS_APIErroLoadCheck(apicallid){                            
  if (!system["dscript"]["cb"][apicallid])
    return false;
  var objekt = system["dscript"]["cb"][apicallid];
if (system["dscript"]["cb"][apicallid]['dontkill'])
    return false;
  // immernoch da, also wahrscheinlich fehler aufgetreten 
  if (objekt.onError)
    objekt.onError(apicallid); 
  delete(system["dscript"]["cb"][apicallid]);
}

Erklärungen zu dem Code
Die erste Funktion ist der eigentliche Aufruf. Diese prüft ob die globale Variable existiert und deklariert gegebenenfalls eine. Genauso werden dann spezielle Variablen angelegt, damit der Apicall, weitere Informationen hintelegen kann. Hier ist natürlich eine Klasse bestimmt einfacher und angenehmer. 
Um statische Inhalte einzulesen, gibt man die Variable urlid mit an. Diese wird dann durch die eingeladene JavaScript Datei und darin durch den JavaScript Funktionsaufruf, als Parameter übergeben, so dass man dann die Antwort identifizieren kann. Bei dynamischen Antworten, kann der Server die entsprechenden Parameter setzen, so dass die urlid nicht nötig ist. Dann wird im Header ein Node erzeugt und die Datei eingelesen. Passiert natürlich asynchron. Damit man erkennen kann, ob ein Fehler beim Einlesen passiert ist, zum Beispiel, weil die Datei nicht vorhanden ist, wird eine Timer gesetzt. Der Standardwert ist auf 5 Sekunden gesetzt, das sollte langen, um auch bei langsamen Verbindungen nicht zu früh abzubrechen und gleichzeitig nicht zu spät den Fehler zu erkennen.
Sobald die Datei eingelesen ist, wird der darin enthaltene Code ausgeführt. Darum sollte dieser Code am besten auch den Funktionsaufruf RS_APIDispatcher(id, result) aufrufen. Als Parameter enthalten dann die URLID und als RESULT das entsprechende Ergebnis. Man sollte wissen, was man als Ergebnis erwartet. Es kann auch neben einem JSON auch eine Funktion sein. Diese wird dann auch gleich aufgerufen. So eine Art Callback. So kann man auch vom Server aus, bzw aus der Datendatei eine Funktion erzwingen. Am besten man erstellt ein JSON als result. Das kann man dann in der Callback-Funktion auswerten. Die Fehlerbehandlungsfunktion ist simple. Sollte diese aufgerufen werden, prüft diese ob nicht die Datei eingeladen wurde. Falls nicht, geht sie von einem Fehler aus und entfernt die Variablen zu diesem Apicall.


Die Datendatei
Die Statische Daten-JavaScript-Datei könnte folgenden Inhalt haben.

RS_APIDispatcher("buchdaten/autor", {'name':'detleff egge', 'titel':'mein buch'});


Beispiel Aufruf

var url = "http://api.domain.com/datendatei.js";
apicall(url,
function(h) {
alert(h.object.name);
  }, 
function(h){alert("error");}, 
10000,
"buchdaten/autor"
);


Fazit
Ist diese Methode einmal eingebunden, kann man auch ohne dynamische Server, ein hohes Mass an Dynamik und Flexibilität erreichen. Sind dynamische Server im Einsatz, muss man auf denen die Ausgabe erst implementieren, aber das ist recht einfach. Der Aufruf erfolgt mit dem Namen der Callback-Funktion und der im JavaScript verwalteten ID. So könnte ein dynamisches Skript diese Daten setzen und dann den Inhalt senden.
Diese Apicall-Methoden werden auch von RapidShare und DirectPayLink unterstützt und erlauben somit eigene Applikationen auf HTML und JavaScript Basis.

Dienstag, 19. April 2011

Effizientes betreiben von Portalen

Worum geht es?
In diesem Beitrag wird eine Vorgehensweise aufgezeigt, die zum Einen sehr kostengünstig im Unterhalt ist und zum anderen extrem viele User bedienen soll. Dabei versuche ich grundsätzliche Überlegungen anzuwenden, um den Bedarf zu erkennen und auch die Notwendigkeit von dynamischen Inhalten zu identifizieren. Dieser Beitrag verlangt eine gewisse Grundkenntnis der Materie. Sie kann nicht als generelle Lösung angesehen werden, doch bietet sie in den meisten Fällen eine interessante Vorgehensweise. Hier wird ganz klar eine abweichende Struktur von Webseiten mit Content gewählt und dadurch bedingt sie auch eine etwas breitere Wissensbasis in Webentwicklung und Server-Knowledge.

Warum weiss ich etwas darüber?
Ich habe von je her immer mit sehr vielen Daten und deren Bereitstellung im Netz zu tun gehabt. Sei es die Entwicklung von Shoplösungen für über 150.000 Produkte mit mehr als 1.000 Hersteller damals schon mit PHP3 über einem Web-Abrechungstool für einen grossen Internetzugangsprovider, der alleine pro Tag 20 Millionen Datensätze angesammelt hat bis hin zu RapidShare Entertainment, die eine Portalseite bereitstellten auf der täglich 500.000 User die Inhalte nutzen.

Diese Problemstellungen sollte alle sehr kostengünstig erstellt werden und darüber hinaus wollte ich immer wert auch Einfachheit legen. Einfach zu warten, einfach zu skalieren, einfach an Ressourcenbedarf.
Hier half mir sehr gut andere Ansätze zu wählen und das Wissen aus den anderen Bereichen, die manchmal einem reinen Programmierer fehlen, da er zu tief in der Materie gefangen ist.

Die Idee
Es gilt einen Plattform zu schaffen, die von vielen User besucht werden soll. Der Traffic soll im Rahmen bleiben, die Server sollen so unkompliziert wie möglich sein, damit ein schneller Wechsel auf andere Systeme und Provider möglich ist. Die Ausfallsicherheit sollte unterstützt werden und das Ganze soll natürlich sehr schnell beim Endkunden wirken. Die Daten werden in einer Datenbank erfasst und der Enduser kann diese, nach Freigabe ins System durch den Verfasser, lesen.

Grundlegende Gedanken
Es gibt sehr viele herangehensweisen. Oft wird eine Lösung gewählt, dass man eine Sprache wählt, um hier eine Einfachheit zu bringen. Doch diese "Faulheit" wird oft sehr teuer erkauft. So benötigt man viele Server um die dynamischen Skripte ablaufen zu lassen. Unter Umständen erhöht sich das Risiko, durch das Einsetzen der Skriptsprachen oder der Server ist sehr schwer und kann nicht eben mal neu gestartet werden bei einem Problem. Auch hier gibt es endlose Möglichkeiten.

Grundlegende Erklärungen
Was ist das schnellste, was wir im Web liefern können? Klar, die statischen Inhalte. Wenn der Webserver, die Anfrage verarbeitet und analysiert hat, kann er den nächsten Prozess anwerfen. Dabei geht er folgender Massen vor. Ist die Anfrage direkt für mich oder für einen Interpreter? Geht es zu einem Interpreter, weckt er den und sendet die Anfrage weiter. Dieser startet, meist müssen noch einige Variablen gesetzt werden und dann lädt er die Ressourcen ein. Interpretiert diese und startet den Ablauf. Ist nun auch eine Datenbankabfrage involviert, muss auch hier erstmal die Verbindung hergestellt werden. Dabei spielt es keine Rolle ob man persistente Verbindungen oder dergleichen nutzt, grundsätzlich muss die Datenbankserver-Verbindung hergestellt oder die bestehende Verbindung initialisiert werden. Nachdem nun alles abgearbeitet wurde, wird der erzeugte Ausgabetext an den Webserver zurück gegeben. Dieser sendet nun die Daten an den Client. Zeitintensive und Ressourcenintesive Operationen sind somit der Einsatz von Interpreter und andere nachgelagerte Helfersysteme. Das Einfachste ist somit wenn man in der Lage ist nur statische Inhalte, am besten noch in den Arbeitsspeicher ausgelagert auszuliefern.

Grundlegende Analyse des Systems
Hier muss  man sich die Applikation fertig vorstellen. Durschspielen, was erfasst werden soll und was gelesen werden soll. Oft ist es so, dass das Schreiben der Informationen nicht so oft passiert und das Lesen der Daten den Hauptteil der Applikation darstellt. Nun ist die Frage, ob das Erstellen der Applikation aus dynamischen Skripten entstehen sollte? Eigentlich kann man mindestens 2 Teile ausmachen. Einen schreibenden und einen Lesenden, der nicht dynamisch sein muss. Oft wird für das Lesen nicht einmal eine Zugangskontrolle benötigt. Warum sollte man hier nun Skripte einsetzen und Datenbankabfragen erstellen, für immer die gleichen Daten? Man ermittelt als Bestandteile, die auch rein statisch sein könnten. Dabei grob nur nach den Bereichen gehen und nicht schon auf den Inhalt direkt eingehen. Sprachen und zeitgesteuerte Analysen sind auch statisch. Oft sind Teile statisch realisierbar, die man nie vermutet hätte. Ganz klar ist das aber teils mit einem höheren Programmieraufwand verbunden, aber das Result rechtfertigt dies alle mal.

Die Aufteilung und Realisierung
Nachdem wir nun die Aufteilung gemacht habe und uns für eine Skriptsprache entschieden haben, können wir den serverseitigen Teil erstellen. Wir nehmen PHP und erstellen die Skripte zum Erfassen der Datensätze, Administration (Login, Rechte, etc) und zum Erstellen der statischen Inhalte. Unser PHP Skript erzeugt zu keiner Zeit HTML welches zum Arbeiten angezeigt wird. Dieser Vorgang hat auch den enormen Vorteil, dass wir die Serverkomponente beliebig austauschen könne. So könnten man um Performance zu erzeugen, auch die fertige Serverkomponente komplett in C inklusive dem Webserver realsieren und laufen lassen. Die Enduser-Seite erstellen wir in HTML. So kommunizieren unsere unabhängigen System mittels einer API. Auch hier der grosse Vorteil, dass ein Austauschen des Anzeigessystems komplett unabhängig vom Server statt finden kann.

Der Serverteil
Unser Skript erhält mittels API calls die Befehle zum Arbeiten. So reduzieren wir die eigentliche Arbeit auf den Server, denn für den anzeigenden Bereich nutzen wir statische Dateien. Somit realisiert unser Server nur die Anfragen und Antwortet auch sehr kurz mit JavaScript Code. Dieser wird dann im Client interpretiert. Dazu mehr später. Wir implementieren, die Einbindung der Datenbank und für den Administrator eine Verwaltung: User verwalten (anlegen, editieren, löschen), Beiträge verwalten (anlegen, editieren, löschen).
Dazu kommt dann die Loginprüfung und die Beitragsverwaltung für den einzelnen Benutzer.
Eventuell werden einige automatisierte Aktionen benötigt, wie das automatische Freigeben von Beiträgen, diese kann man auch wunderbar implementieren und später entweder per HTTP-Request oder über einen Consolen Aufruf starten. Ein Cronjob einrichten und fertig ist die Sache. Sobald eine Beitrag fertig ist speichert der Server nicht nur in der Datenbank, sondern auch auf eine Datei mit dem Inhalt. Diese Datei ist
in einem speziellen Format, so dass der Client diese per JavaScript Call einlesen kann und den Inhalt interpretieren kann. Hierzu gibt es verschiedene Herangehensweisen. Eine von mir bevorzugte werde ich in späteren Beiträgen erläutern. Vorerst kann könnte man AJAX einsetzen.

Passwortschutz für Verzeichnisse und Dateien
Grundsätzlich sollte man alles einfach halten, dass heisst nicht, dass das Erstellen einfach ist, sondern, dass wenige Komponenten eingesetzt werden und ein schnelles Protieren auf Backup oder Skalierserver möglich ist. Was könnten wir machen, wenn wir Dateien haben die gesichert sein sollten. Hier erstmal ein Mini-Ausflug in HTTPS. Die SSL Verbindung wird zum Server gemacht. Hierbei stellt der Client erstmal die Verbindung her, bevor er preis gibt, was er vom Server möchte. Man tauscht Schlüsselinformationen auf, so dass eine verschlüsselte Verbindung entstehen kann. Nachdem diese Verbindung steht, fragt der Client die Dateien an. Somit ist sogar der Request der Datei samt Pfad verschlüsselt.
Hier ist der Ansatz ausgelagert. Man könnte 2 oder mehr Schlüssel (zufällige alphanumerische Zeichenfolgen erstellen; also wie Login und Passwort). Diese Schlüssel werden wir um Verzeichnisse zu erstellen. Der Server sollte die Indexe nicht listen, aber um sicher zu gehen, legen wir in jedes Verzeichnis einfach eine Dateie ab die vom Server in der Regel interpretiert wird. Diese Datei muss natürlich schon im vorgelagerten Ordner liegen, da sonst der Ordner aufgelistet wird. Genauso kann man auch nur einzelne Dateien schützen, ist jedoch dann nicht so einfach bei einem Wechsel der Schlüssel alle betroffenen Dateien anzupassen. Deshalb empfehle ich einen Ordner, der ist schnell umbenannt. Diese Methode hat gegenüber HTACCESS auch den Vorteil, dass sie Webserver und Dateisystem unabhängig ist. Also ideal zum portieren.

Die Ausgabe, bzw die Enduser Seite
Der Enduser bekommt eine HTML-Seite. Diese wird mittels JavaScript die nötigen Daten holen, aufbereiten und anzeigen. Somit ist die Performance auf den Client ausgelagert. Die Serverkomponente muss nur sicherstellen, dass die Daten geschickt aufbereitet statisch vorliegen. Der Aufbau der eigentlichen Webapplikation erfolgt mittels Clientseitigem Skript und Nachladen von Elementen in den laufenden HTML Code. So muss serverseitig kein Skript angeworfen werden und keine Datenbank durchforstet werden. Die Applikation könnte eine statische Datei einlesen, welche die Kategorien der Beiträge enthält und diese auflisten. Wenn diese Daten angezeigt werden sollen, wird nach dem gleichen Prinzip wiederum eine Datei eingelesen, welche die Inhalte einliest. Der eigntliche Beitrag, kann ebenfalls in einer Datei ausgelagert sein, so dass der Datenaustausch schnell ist, da nur wenige benötigte Daten gesendet werden. Hier ist auch ein grosser Vorteil, dass die Daten keine Design Elemente enhalten und das Design der Seite ansich durch andere Dateien gesteuert wird. Mehrsprachigkeit, kann genauso realisiert werden, da man das komplette Design auch über statische Dateien einlesen kann. Das ganze bringen wir mittels JavaScript (.innerHTML oder Nodes) im Browser zur Anzeige.

Zu beachten und Überlegungen
Bei dieser Methode existieren unter Umständen sehr viele Dateien auf dem Server. Hier sollte man vorher die Masse an Dateien überdenken und ggf die Order gleich in 1.000 Schritte füllen. Bei Datendateien zum Beispiel könnte man pro Ordner immer 1.000 Objekte speichern und die nächsten in tiefer gelegene Ordner.
Also wenn der Datensatz zum Beispiel die ID: 123456789 hat, könnte der Speicherort sein: d/123/456/89/datensatz.js. Auch hier ist anzuraten, dass die Datendatei nicht Bestandteil der ID ist, so hat man gleich einen Container für weitere Infos, wie Screenshots und dergleichen. Der Zugriff einer einzelnen Datei geht immer sehr schnell, das Listing jedoch von Ordner die sehr viele Dateien enthalten, kann unter Umständen auch abbrechen. Mit der tausender-Regel sollten kaum Probleme auftreten, die ist performant genug und bietet auch ausreichend Kapazitäten. Die Serverkomponente wird nun nicht mehr so stark belastet und man kann einige statische Auslieferungsserver betreiben, während der dynamische Server alleine sein kann und unter Umständen auch nicht mal im Internet. So könnte nach dem Erstellen der Dateien, ein anderer Job diese Dateien auf die statischen Server kopieren. Hier hat man zwar eine Verzögerung, jedoch ist diese heutzutage gering. Nachteil ist hierbei, dass sehr viele Dateien im Voraus erstellt werden müssen und vieles ist redundant. Doch da diese durch den Server erstellt werden, sollte dies nicht schwerwiegend sein. Ein manuelles Eingreifen ist nur bedingt noch möglich und mit viel Aufwand verbunden. Aber gerade einige Listen ändern sich sehr selten und da kann man optimieren mit speziellen Cronjobs, die dann die Daten bei Leerlauf oder dergleichen abarbeiten.

Aus der Praxis
Wir haben damals die RapidGames.com und die dazugehörigen RapidMusic, RapidMovies nach dem gleichen Prinzip erstellt. Ein Server im Office war für die dynamischen Inhalte zuständig und die erzeugten Dateien wurden auf die statischen Server verteilt. Das lief so hervorragend, dass unser statischer Backserver eigentlich nie zum Einsatz kam. Auch ein Loadbalancing war nie nötig, bis zu dem Tag, an dem scheinbar unser Provider doch mehr Kunden aufnahm als verkraftbar. Wir haben damals einen statischen Webhosting Vertrag für ca. 15 CHF gehabt und damit auch 500.000 User pro Tag versorgt. Günstiger geht es kaum noch. Zumal die Lösung durch eine einfache index.html Datei sofort skalierbar war, die dann Anfragen an andere statische Server hätte weiterleiten können. Nicht mal Zugriff auf den Server ist dafür nötig. Sollte der Andrang so gross sein, dass selbst die Hits zuviel sind, kann man DNS Load-Balancing einsetzen. Einfach weitere IPs zum Domain-Eintrag ergänzen. Keep it simple. Klar dass dies nicht immer die saubersten Lösungen sind, aber bis her immer die unkompliziertesten und effektivsten. Man sollte sich immer Out-of-the-Box denken und sich die Mittel ansehen und sich fragen, wie kann ich mit den bestehenden Mittel das Ergebnis erreichen?

Saso Nikolov