Dienstag, 25. Dezember 2012

Fun with Google's Appengine: Surfin’ U.S.A.

Pünktlich zu Weihnachten gibts von uns dieses Jahr ein kleines Script für alle, die mal schnell eine "Ist in deinem Land nicht verfügbar" - Nachricht umgehen müssen.

Die Appengine bietet neben vielen Tutorials und Beispielanwendungen auch die Möglichkeit, über shell.appspot.com direkt Sachen innerhalb der GAE auszuprobieren. Das Script gaeproxy.py erzeugt einen lokalen HTTP-Proxy auf Port 4321, der alle Anfragen über diese Shell tunnelt. Damit werden die Anfragen von einer IP in den USA (74.125.182.35) an die jeweilige Webseite gestellt und umgehen so länderspezifische Webblockaden.

z.Z. unterstütz das Script nur einfache HTTP-Gets mit einer maximalen Größe von c.a. 500KB.
Cookies, HTTP-Post und Antwort-Größen bis ~1Mb würden sich noch recht einfach implementieren lassen; für HTTPS-Unterstützung währe der Aufwand schon recht groß.
Hinweis: Die Verbindung vom Proxy->shell.appspot.com ist nicht verschlüsselt!

Proxy herunterladen

Montag, 24. Dezember 2012

Sicherheit in der GAE - Teil 3

Die Sandbox

Alle Anwendungen der GAE werden in einer Sandbox ausgeführt. Diese stellt Anwendungen einen eingeschränkten Interpreter für die jeweilige Sprache sowie eine API für alle von der GAE bereitgestellten Zusatzdienste bereit. Gleichzeitig erzwingt sie die Abgrenzung zwischen der Anwendung und der darunter liegenden Plattform.

Anwendungen werden beim Upload in der GAE in einen Anwendungscontainer zusammengefasst. Dieser enthält alle Anwendungsdateien und Ressourcen, jedoch nicht die Verzeichnisse, die über die Konfigurationsdateien direkt als statischeVerzeichnisse nach außen freigegeben sind. Der Anwendungscontainer ist read-only, d.h. es ist nicht möglich aus einer Anwendung heraus in das Dateisystem zu schreiben. Dies schützt die Anwendung im Falle einer Kompromittierung vor Manipulationen des Programmcodes und der in diesem Container mitgelieferten Ressourcen [1]. Bis Version 1.7.2 war die Anzahl der Dateien aus der die Anwendung bestehen kann (inkl. der statischen Dateien und Ressourcen) auf 10.000 und die Gesamtgröße dieses Containers auf 1Gb beschränkt [2].


Abschirmung der Plattform

Die Sandbox verhindert den Zugriff auf die darunterliegende Plattform und das Betriebsystem, indem verschiedene Funktionen des Interpreters abgeschaltet sind. Im Gegensatz zur Java-Version der GAE, bei der einzelne Klassen der Standardbibliothek freigeschaltet sind [3], sind bei Python fast alle Module, die in Python selbst geschrieben sind, verfügbar. Allerdings sind nahezu alle Erweiterungen, die in C geschrieben sind, entfernt. So sind neben dem ctypes-Modul, das Zugriff auf Funktionen von externen Librarys ermöglicht, auch das _socket Modul nicht verfügbar.

Abschirmung des Internets

Neben dem Schutz der Anwendung und der Plattform selbst sind auch Maßnahmen ergriffen, um andere Systeme und Netzwerke im Internet vor Angriffen aus der GAE heraus zu schützen. So besteht keine Möglichkeit aus der GAE eine direkte Verbindung zu anderen Systemen im Internet aufzubauen. Für das abrufen von URLs steht ein Proxy zur Verfügung, der verschiedene HTTP-Header in der ausgehenden Anfrage erzwingt [4]. Für den Versand und Empfang von E-Mails steht ebenfalls eine API zur Verfügung, die die E-Mails über einen Proxy leitet und so beispielsweise Missbrauch durch eine gefälschte Absenderkennung verhindert [5]. Allerdings wird die Möglichkeit, direkt ausgehende IP-Verbindungen zu erlauben z.Z. von Google getestet [6, App Engine 1.7.2 Released]. Damit könnten diese Beschränkungen in einer zukünftigen Version entfernt werden.

[1] Python Runtime Environment
https://developers.google.com/appengine/docs/python/runtime

[2] Google App Engine Python SDK Release Notes
http://code.google.com/p/googleappengine/wiki/SdkReleaseNotes

[3] The JRE Class White List
https://developers.google.com/appengine/docs/java/jrewhitelist

[4] URL Fetch Python API
https://developers.google.com/appengine/docs/python/urlfetch/overview

[5] Mail Python API
https://developers.google.com/appengine/docs/python/mail/overview

[6] News, Notes, tips and tricks from the Google App Engine Team
http://googleappengine.blogspot.de


Sonntag, 23. Dezember 2012

Sicherheit in der GAE - Teil 2

Funkionsweise der GAE

Die GAE ist ein verteiltes System zum Bereitstellen von Webanwendungen. Die GAE liegt z.Z. in drei Versionen vor: Python (wahlweise in den Versionen 2.5 oder 2.7), Java oder in der von Google selbst entwickelten Programmiersprache Go. Anwendungen werden zunächst lokal mittels einem von Google bereitgestellten Software development Kit (SDK) erstellt. Dieses simuliert die von der GAE bereitgestellten Application programming interfaces (APIs) und stellt einen lokalen Webserver bereit, auf dem die Anwendung getestet werden kann. Anschließend wird die Anwendung „deployed“, d.h. mittels eines mit dem SDK mitgelieferten Programms in die GAE hochgeladen. Hierbei werden die Anwendungsdaten in einem Container zusammengefasst, der beim Aufruf der Anwendung von einem Server der GAE geladen und die Anwendung hieraus ausgeführt wird. Für den Betrieb der Anwendung stellt Google verschiedene Dienste wie Datenbanken oder einen Dienst zum versenden von E-Mails bereit. Für die Nutzung dieser Dienste entstehen entsprechend dem Nutzungsvolumen Kosten. Allerdings stellt Google jeder Anwendung einen gewisse Menge an Ressoucen pro Tag kostenlos zur Verfügung, sodass die Appengine risikofrei getestet werden kann. Weiterhin gelten für verschiedene Dienste sogenannte Safety Quotas [1], die verhindern, dass einzelne Anwendungen einen Dienst überlasten und dieser so für andere Anwendungen der GAE vorübergehend nicht zur Verfügung steht. Diese Limits dienen ausschließlich zur Planung der benötigten Ressourcen und stellen keine technischen Beschränkungen dar, sodass diese auf Anfrage [2] erhöht werden können. Konkret sind folgende Ressourcen von Bedeutung, da diese von den meisten Webanwendungen verwendet werden.

  • Ein- und ausgehender Traffic: Für freie Anwendungen gelten Limits von je 1GB Traffic/Tag sowie maximal 56MB/Minute. Bezahlte Anwendungen haben z.Z. unbegrenzten, kostenlos eingehenden Traffic sowie ausgehenden Traffic für 0.12$/GB (je 1GB/Tag kostenlos; Maximum 10GB/Minute).
  • Datenbank: Freie Anwendungen erhalten je 50.000 Schreib-, Lese- und Small-Ops1 pro Tag. Bezahlte Anwendungen können diesen Dienst beliebig für 0,1$/100k Schreiboperationen, 0,07$/100k Leseoperationen und 0,01$ 100k Smallops nutzen. Dies sind jedoch low-level Operationen; das Einfügen eines Eintrags benötigt ggf. mehrere dieser Schreiboperationen [3].
  • URL-Fetch: Freie Anwendungen können diesen Dienst zum Abruf entfernter URLs höchstens 657.000 mal pro Tag nutzen. Weiterhin gelten für sie Beschränkungen von maximal 3.000 Zugriffen/Minute mit je maximal 22MB/Minute gesendete oder empfangene Daten. Die über diesen Dienst übertragenen Daten zählen ebenfalls zum ein- und ausgehenden Traffic der Anwendung. Für bezahlte Anwendungen gelten die gleichen Vorraussetzungen, jedoch besteht hier ein Tageslimit von 46.000.000 Zugriffen/Tag sowie maximal 32.000 Zugriffe/Minute mit je maximal 740MB übertragenen Daten pro Minute.
  • Instanzstunden: Die GAE startet Instanzen der Anwendung nach Bedarf. Sinkt der Bedarf, wird eine Instanz 15 Minuten nach dem letzten Zugriff auf sie entweder abgeschaltet oder weiterhin vorgehalten, jedoch bis zum nächsten Zugriff nicht weiter angerechnet. Freie Anwendungen erhalten 28 Instanzstunden/Tag, bezahlte Anwendungen können darüber hinaus weitere Instanzstunden für 0.08$/Stunde nutzen.

[1] Quotas and Safetylimits
https://developers.google.com/appengine/docs/quotas

[2] Quota Increase Request
http://support.google.com/code/bin/request.py?\&contact_type=AppEngineCPURequest

[3] Billing and Budgeting Resources
https://developers.google.com/appengine/docs/billing

Freitag, 21. Dezember 2012

Einführungsinterview zu ViUR


Gestern haben wir das Einführungs-Interview zu ViUR bebildert und auf den neuen Youtubechannel: weloveViUR gestellt. Über Weihnachten sollen noch 2-3 Videos folgen zu:

  • Inbetriebnahme von ViUR-Projekt unter Windows
  • Installation und Kurzeinführung zu ViUR Admin
  • Ändern der Nutzerauthentifizierung bei ViUR-Projekt von von ViUR verwalteten zu Googlekonten
... und vielleicht gibt es ja vor Weihnachten noch eine kleine Überraschung....

Donnerstag, 20. Dezember 2012

Neuer Renderer für ViUR: RSS

Seit heute besitzt ViUR einen neuen Render für RSS-Feeds.
Mit diesem lassen sich Einträge aus Listen als RSS-Feeds abonieren. Hierfür muss dieser Render für die jeweilige Anwendung aktiviert werden und eine Konfiguration ähnlich der adminConfig erstellt werden.

Beispiel:
rssInfo = {
  "feed": #Einstellungen für den gesamten Feed
  {
    "title": "My RSS Feed" ,  #Name des Feeds
    "link": "http://www.example.com", #Link zur Seite
    "descr": "An example",  #Beschreibung
    "lang": "en",  #Sprache des Feeds
    "author": "Me, Myself and I",  #Von wem
    "date": datetime.now(), #Erstellungsdatum der aktuellen Datei
    "image": #Bild zum Feed (Optional)
    {
      "url":"https://example.com/image.jpg" #URL zum Bild
      "title":"My RSS Feed", #Bildbeschreibung (idr. Identisch zum Titel)
      "link":"https://example.com/" #Link zum Bild
    }
  }
  "item": { #Konfiguration der einzelnen Einträge
    "title": lambda skel: skel.name.value, #Titel eines Eintrags im Feed
    descr": lambda skel: skel.descr.value[ : 255],  #Beschreibung des Eintrags
    "link": lambda skel: "http://www.example.com/calender/view/%s" % skel.id.value,  #Link
    "id": lambda skel: skel.id.value,  #UID des Eintrags
    "date": lambda skel: skel.creationdate.value #Erstellungsdatum
    }
  }

Hierbei sind als Werte jeweils
  • Strings (werden unverändert ausgebeben)
  • Funktionen (Im Block "feed" wird kein Parameter, unter "item" jeweils das aktuelle Skeleton übergeben)
  • Datetime-Objekte (diese werden entsprechend RFC 2822 kodiert)
möglich.

Anschließend stehen die Einträge der jeweiligen Liste unter /rss/anwendungsname/list zur Verfügung. Andere Funktionen (View, Edit, ... ) sind mit diesem Render nicht möglich. Die Auswahl der enthaltenen Elemente kann wie gewohnt über GET-Parameter der URL angepasst werden.

Dienstag, 18. Dezember 2012

Sicherheit in der GAE - Teil 1

Webanwendungen stellen inzwischen eine Kernkomponente des Internets dar. Ein Großteil des Umsatzes im E-Commerce wird über Webanwendungen abgewickelt. Allein in Deutschland wird der Umsatz im E-Commerce für 2012 auf 29,5 Milliarden Euro geschätzt, 13% mehr als in 2011 [1]. Bedingt durch diese rasant steigende, gesamtwirtschaftliche Bedeutung von Webanwendungen rückt deren Sicherheit und Verfügbarkeit zunehmend in den Fokus.

Die GAE ist deshalb mit mehren Sicherheitsaspekten versehen, die Angriffe auf Anwendungen erschweren und im Falle von erfolgreichen Angriffen die Auswirkungen auf die Anwendung minimieren sollen. Weiterhin wurden diverse Maßnahmen ergriffen, um die GAE selbst zu schützen und negative Effekte von einer Anwendung auf andere Anwendungen zu minimieren.

 Im Laufe dieser Serie werden mögliche Angriffe auf die Verfügbarkeit einer Anwendung der GAE (Python) aufgezeigt und Gegenmaßnahmen vorgestellt.

[1] Handelsverband Deutschland: E-Commerce-Umsatz in Deutschland von 1999 bis 2011
http://de.statista.com/statistik/daten/studie/3979/umfrage/e-commerce-umsatz-in-deutschland-seit-1999

Montag, 17. Dezember 2012

Grundlagen: Google® Bigtable


Grundlagen: Google® Bigtable

Google wurde 1998 gegründet und nutzt seit 2005 Bigtable für eine steigende Zahl an Diensten, wie die Indizierung von Web-Seiten für die Google Websuche über Google Earth bis hin zu Google Finance. Mit der Platzierung der Google-App-Einginge (GAE) als PaaS-Leistung am Markt stellte Google einen durch API (Application Programming Interface) gekapselten Zugang zu Bigtable bereit.

Google erschuf auf Basis vorhandener OpenSource-Technologien und unter dem Paradigma der Shared nothing Architektur eine eigene proprietäre verteilte Datenbank, die sich an vorangegangenen, vergleichbaren Datenbanken, wie IBM’s DB2 Parallel Edition & Oracles Real Applicationcluster database, orientierte.
Google-Bigtable hat sowohl Eigenschaften spaltenorientierter als objektorientierter Datenbanken. Intern werden die Daten als unabhängige, verteilte, persistente, mehrdimensionale, geordnete Karte angesehen („A Bigtable is a sparse, distributed, persistent multi-dimensional sorted map.“). Durch die GQL wird dem Entwickler ein spaltenorientierter Zugang bereitgestellt. Über die von der  Appengine bereitgestellten Klassen „Model und Expando“, wird hingegen eine objektorientierte Sicht zugänglich gemacht.

Zusammenhang zwischen Bigtable, Datastore, HRD / Master/Slave

Aus Applikationssicht ist Google Datastore das System, welches die Datenbank repräsentiert. Durch die Datastore-API ist der Zugriff auf die Megastore-Library möglich, welche Operationen auf dem verteilten System für strukturierte Daten, der Bigtable, ermöglicht. Bigtable setzt auf das Googlefilesystem (GFS) den Chubby Lockservice sowie weitere Programme auf. Der Replikationsserver verwaltet und synchronisiert die Datenmanipulationen über die verschiedenen Replikationen der Bigtable.

Chubby ist als Service in der Google-Infrastruktur die zentrale Anlaufstelle für Server, die ihren Verbund koordinieren müssen. So hält Chubby die Metadaten des Verbundes und entscheidet, wer in diesem als Master fungiert. Da die Chubby-Knoten sehr lose gekoppelt sind und jederzeit mit Fehlern und Ausfällen gerechnet wird, ist dieser Service optimiert auf Verlässlichkeit und weniger auf Performance.



Darstellung und Speicherung von Entitäten

Aus der Applikationssicht gibt es zwei Sichten in der die in Bigtable gespeicherten Daten repräsentiert werden. Zum einen besteht die Möglichkeit Klassen mit vordefinierten Attributen zu verwenden, die die Vorzüge aus der objektorientierten Programmierung, wie Vererbung bietet. Andererseits besteht die Möglichkeit freibleibende leere Objekte zu erzeugen und diese mit zur Laufzeit frei wählbaren Attributen und Werten zu füllen. Jedes Objekt bzw. jeder Eintrag stellt eine Instanz einer Entitätsklasse dar. Die Standarddatentypen: String, Text, Int, Float, Boolean sowie Null werden unterstützt. Zusätzlich sind noch Datentypen, wie geographischer Punkt, E-Mailadresse, Post-Adresse etc. zu erwähnen. Des Weiteren steht ein Datentyp zur Referenzierung auf andere Entitäten zur Verfügung. Außerdem ist es möglich jedem Attributsschlüssel mehrere Werte zuzuweisen, so dass ein Attribut eine Liste von Werten darstellen kann (auch von unterschiedlichen Datentypen).


Abbildung 3: Datenmodellierung mit Vererbung in Python
Jedes einmal gespeicherte Objekt hat neben seiner nicht änderbaren ID einen Typ, der durch die Klasse, die dieses Objekt erzeugt und gespeichert hat bestimmt wird. Anhand dieses Typs werden die gespeicherten Objekte für Zugriffe gruppiert, so dass z.B. Abfragen zum Typ „Auto“ keine Elemente des Typs „Person“ liefern. Zur internen Repräsentation werden die Daten nach Einträgen mit Attributen zerlegt und in der Bigtable gespeichert. Diese besteht aus Tupeln dessen Deskriptoren Zeilenname, Spaltenname und Zeitstempel einem Wert zugewiesen werden, siehe Abbildung 4. Jeder gespeicherte Datentyp wird in eine String-Repräsentation serialisiert.  Besteht der Wert aus einer Liste von Werten, wird für jedes Element ein eigenes Tupel von der Bigtable gespeichert. Bei Änderungen von Einträgen werden die alten Werte in der Bigtable nicht überschrieben, sondern es werden neue Tupel mit neuerem Zeitstempel eingefügt. Um die Größe der Datenbank zu begrenzen werden durch Hintergrunddienste in freikonfigurierbaren, periodischen Intervallen obsolete Tupel nach vorgegebenen Kriterien gelöscht.

Abbildung 4 Speicherung der Daten in Bigtable am Beispiel: Webseitencaching, Zeilenname ist die umgekehrte URL, Content-Spalte besitzt mehrere Versionen, t6 ist die aktuelle, Anchors werden einer beliebigen Anzahl von Spalten gespeichert.
Quelle: F. Chang, R. E. Gruber und weitere Authoren, Bigtable: A Distributed Storage System for Structured Data, Abschnitt 4.3

Abfragen und Indizes

Abfragen in der GAE werden über sog. Query-Objekte an die Bigtable übermittelt. Dazu wird ein Queryobjekt unter Angabe der zu verwendenden Klasse erzeugt. Anschließend können diesem die gewünschten Restriktionen, Filter und Sortierungsangaben hinzugefügt werden. Diese stellen eine Teilmenge im Vergleich zu anderen (relationalen) Datenbanksystemen dar. Insbesondere kann sich IN nicht auf Subqueries beziehen, sondern nur auf der Abfrage im Vorhinein übergebene Listen.

Aufgrund der Auslegung für große Datenmengen kann Bigtable nur im Voraus bekannte Abfragen durchführen, da sie nicht einzelne Datensätze scannt, sondern in Frage kommende Datensätze aus sog. Indizes extrahiert. Die Indizes sind sortierte Listen von Dokumenten, die nur eine echte Teilmenge von Schlüsseln enthalten.


Abbildung 5: Index des Level-Attributs der Spieler-Entität, absteigend sortiert

Wie die Abbildung zeigt, werden für jeden Schlüssel eines Dokuments automatisch zwei sog. Elementarindizes angelegt, die jeweils einmal in auf- und absteigender Reihenfolge sortiert sind. Anhand dieser können einfache Abfragen, wie z.B. „Gib mir alle Spieler die einen Level zwischen 12 und 10 haben“ ausgeführt werden. Bigtable sucht dabei den ersten in Frage kommenden Eintrag, sowie den ersten danach nicht mehr in Frage kommenden Eintrag und gibt alle dazwischenliegenden Einträge aus. Aufgrund dieses Verfahrens reichen die Elementarindizes nicht für alle Anfragen aus. Die Frage nach „Alle Spieler mit Level zwischen 10 und 13 und der Charakterklasse Magier“ lässt sich weder mit dem Elementarindex für Level noch dem Elementarindex für charclass beantworten. Hierfür ist ein zusätzlicher Index nötig, der die beiden Attribute verbindet und entsprechend sortiert ist.


Abbildung 6: Index des Level- und des Charackterklassen-Attributs der Spieler-Entität, aufsteigend sortiert nach Klasse und dann nach Level

Der Index aus Abbildung 6 enthält die Attribute Charackterklasse und Level und ist zuerst nach Charackterklasse und innerhalb dieser nach level sortiert. Somit kann die Bigtable zunächst zum ersten Eintrag mit Charackterklasse=Magier springen und von dort weiter springen, bis zum ersten Eintrag in dem auch das Level-Attribut die Bedingungen erfüllt. So wird auch der letzte in Frage kommende Eintrag gefunden und die Einträge dazwischen ausgegeben. Dieses Verfahren garantiert, dass alle Suchanfragen an die Datenbank in nahezu konstanter Zeit (O(1))beantwortet werden können. 

Transaktionen

Um die Integrität von Daten zu gewährleisten, unterstützt auch die Bigtable Transaktionen. Aufgrund der Fokussierung auf sehr große Projekte, wird aber nicht mit dem in RDBS üblichen Sperrverfahren, sondern mit einem sog. „optimistic concurrency control“ – Verfahren gearbeitet.  Bigtable ermöglicht durchgehende und konsistente Lesezugriffe, auch während an den zu lesenden Objekten Veränderungen vorgenommen werden. Dadurch, dass Bigtable Veränderungen an Einträge als neue Versionen eines Eintrags mit jüngerem Zeitstempel speichert, besteht für Lesezugriffe die Möglichkeit eine konsistente Sicht auf die Daten anhand des Zeitstempels des ersten Lesezugriffs zu erlangen.

Abbildung 7:  Beispiel: Umgang mit Transaktionen und Entitygroups
Quelle: Eigene Darstellung


Die Bigtable führt Buch über den Zeitpunkt des Beginns einer jeden Transaktion, sowie dem Zeitpunkt der letzten Änderung. Liest ein Client innerhalb einer Transaktion Daten aus der Bigtable, erhält dieser ausschließlich Einträge mit einem Zeitstempel „kleiner gleich“ dem Zeitpunkt des Beginns der Transaktion.
Somit ist sichergestellt, dass die Sicht auf die Daten konsistent bleibt, auch wenn in der Zwischenzeit Daten geändert werden.

Werden nun innerhalb der Transaktion Daten geändert, werden diese Änderungen gesammelt und mit Ende der Transaktion als atomische Operationen angewendet, sofern die Daten seit Beginn der Transaktion nicht verändert wurden. Falls dies geschehen ist, werden die Änderungen vollständig verworfen und der Client über den Fehlschlag seiner Transaktion informiert.

Um trotzdem mehrere Transaktionen gleichzeitig zu ermöglichen, werden die Einträge in der Bigtable zu sog. Entitygroups zusammengefasst. Die Entitygroups bilden Bereiche von zusammenhängenden Einträgen. Einträge werden beim Einfügen in die Bigtable zu genau einem frei wählbaren Bereich zugeordnet, der jedoch nachträglich nicht mehr verändert werden kann. Verschiedene Bereiche können parallel von Transaktionen verändert werden, jedoch kann sich eine Transaktion nur auf Daten eines Bereichs beziehen.

Skalierung und Replikationsmechanismen

Für die Bigtable sind zurzeit zwei Replikationsmechanismen verfügbar. Einerseits die Master/Slave Replikation, bei der ein Datacenter als Master ausgewählt wird, und sämtliche Schreibzugriffe über dieses Center abgewickelt werden. Hierbei werden die Änderungen asynchron auf andere Datacenter repliziert.  Andererseits wird der High Replication Datastore angeboten, der mittels des sog. Paxos-Algorithmus die Schreibanfragen auf verschiedene Datacenter verteilt und diese dann synchron auf andere Datacenter repliziert. Bigtable besitzt eigene Algorithmen, die eine automatische Lastanpassung (Skalierung) durchführen, daher ist kein manueller Eingriff notwendig. Die Rechenzentren von Google sind für eine automatische Kapazitätsanpassung an variierende Lasten konzipiert.


Quellen

  • Chang: Bigtable: A Distributed Storage System for Structured Data 
  • Mike Burrows, Google Inc. The Chubby lock service for loosely-coupled distributed systems
  • http://www.google.de/intl/de/about/corporate/company/index.html
  • http://code.google.com/intl/de-DE/appengine/docs/python/datastore/overview.html
  • Sanderson: Programming Google Appengine 2009





Freitag, 14. Dezember 2012

Hello Viurld!

Vor ein paar Tagen haben wir unser Informationssystem ViUR open-source released. Wir arbeiten intern seit circa drei Jahren an und mit diesem selbstentwickelten, modulbasierten Framework. Anfangs mit SQL, später MongoDB und inzwischen auf Basis der Google App Engine™ Infrastruktur.

Dieses Blog wird in den kommenden Tagen und hoffentlich Monaten und Jahren einen kleinen Einblick in unsere Arbeit geben, unseren gedanklichen Ansatz dokumentieren und probieren Probleme zu lösen.