Boesebeck.biz
Blog über die IT

Willkommen auf dem boesebeck.biz blog

Dieses Blog wird einige Themen meines beruflichen Alltags beleuchten, das reicht von Scrum, über technische Themen bis hin zu Organisation und Ähnliches


Suchergebnis: 15

<< 1 ... >>


Kategorie: Allgemeines

Worauf es beim Homepage-Layout ankommt

Di, 17. 07. 2018 - Tags:

Ursprünglich veröffentlicht auf: https://boesebeck.name

Anm.: Dieser Text wurde zur Verfügung gestellt von homepage-erstellen.de

Worauf es beim Homepage-Layout ankommt

Hat man erst einmal die ersten Hürden bei der Erstellung einer Homepage gemeistert, muss man sich um ein passendes Layout kümmern. Ein übersichtliches und ansprechendes Layout sorgt dafür, dass relevante Inhalte leichter gefunden werden können und Seitenbesucher eher zurückkehren.

Was ein gutes Layout auszeichnet

Beim Layout einer Homepage gilt es zunächst darauf zu achten, welchem Zweck die Homepage dienen soll. Soll ein Produkt vorgestellt werden? Möchte man über die Dienstleistung einer Firma informieren? Oder nutzt man die Homepage, um über ein persönliches Anliegen aufzuklären? Wichtig ist, dass alle relevanten Informationen jederzeit gefunden werden können. Ein gutes Layout besteht aus Überschriften, Bildern, Fußzeilen und Spalten. So werden Informationen sinnvoll vorgefiltert und können schon mit wenigen Blicken erfasst werden. Das erhöht für den Besucher der Seite den Bedienkomfort und die Wahrscheinlichkeit, dass man zu einem späteren Zeitpunkt die Seite nochmal aufsuchen wird. Zuerst werden beim Layout Farben und Formen wahrgenommen. Ein farbenfrohes Layout kann sich zum Beispiel für ein Portfolio eignen, das Kreativität ausdrücken soll, passt aber kaum zu bestimmten Firmen oder Dienstleistern. Bei diesen ist es wichtig, dass man die Informationen zu jedem Produkt sofort finden kann.

Seitenleiste zum Teil sehr nützlich

Laut www.homepage-erstellen.de kann sich eine Seitenleiste als sehr nützlich für Besucher der Seite erweisen. Dort sollten aber nicht die wichtigsten Inhalte, sondern hauptsächlich ergänzende Informationen zusammengefasst werden. Die Ausrichtung spielt dabei keine große Rolle und die Seitenleiste kann sowohl auf der rechten als auch auf der linken Seite angebracht werden. In der oberen linken Ecke sollte sich ein Logo befinden. Bei E-Commerce-Seiten ist der Warenkorb meist in der rechten Ecke angebracht. Das Suchfeld befindet sich oftmals direkt neben dem oder in direkter Nähe zum Warenkorb.


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

Custom Caching in Morphium

So, 20. 05. 2018 - Tags: java mongodb morphium cache

Ursprünglich veröffentlicht auf: https://caluga.de

Seit eine der ersten Versionen hat Morphium einen Cache eingebaut, der durch Annotations (Cache) konfiguriert wird.

Dieser Cache wurde in vielen Projekten genutzt und hat gezeigt, dass er gut funktioniert, auch das Synchronisieren des Caches über das Morphium Messaging System funktioniert tadellos.

Jedoch gibt es einige Implementierungen da draußen, die viele neue Features bieten. Und es macht wirklich nur bedingt Sinn, den alle diese Features auch in Morphium einzubauen. Stattdessen haben wir uns entschlossen, mit der aktuellen Betaversion den JCache-Standard (JSR107) zu unterstützen.

Dazu mussten einige Dinge angepasst werden, insbesondere das MorphiumCache-Interface.

Morphium selbst besitzt ja schon immer die möglichkeit, eine eigene Custom-Cache Implementierung zu nutzen. Jedoch ist so was natürlich nicht wirklich einfach machbar. Jedoch nutzen wir diesen Umstand um jetzt beide Cache Implementierungen integrieren zu können:

  1. Die Default Cache implementierung, die sich seit Jahren bewährt hat
  2. Eine JCache-Implementierung, welche auf eine beliebige JCache-Library (z.B. ehcache) zugreifen kann

Damit Morphium, wie immer schon, out of the Box lauffähig ist, haben wir auch noch eine JCache-Implementierung des bewährten Caches gebaut.

How to use

Mit der neuen V3.2.2BETA (via maven central oder auf github )wird Morphium per default die JCache-IMplementierung verwenden. Möchte man stattdessen lieber die neue Jcache-IMplementierung nutzen, muss man dies in der Configuration nur eintragen:

    MorphiumConfig cfg=new MorphiumConfig();
    cfg.setCache(new MorphiumCacheImpl());

für die json oder property file Konfiguration, muss nur die klasse gesetzt werden:

  cacheClassName=de.caluga.morphium.cache.MorphiumCacheImpl

JCache Unterstützung

Wenn man alles auf Default lässt,wird die JCache API genutzt. Standardmäßig wird zunächst die Default-Cache implementierung via Caching.getCachingProvider().getCacheManager() genutzt.

Möchte man lieber seine eigenen Caches eintragen, so ist dies problemlos möglich. Dei Morphium JCache-Implementierung benötigt "nur" einen CacheManager. Die Caches selbst werden von Morphium angelegt und verwaltet.

  CachingProvider provider = Caching.getCachingProvider();
   morphium.getCache.setCacheManager(provider.getCacheManager());

natürlich kann man via code hier noch eigene Einstellungen vornehmen.

BTW: möchte man die Morphium eigene JCache-IMplementierung auch anderswo nutzen, so kann man dies tun, indem man die JCache Implementierung über -Djavax.cache.spi.CachingProvider=de.caluga.morphium.cache.jcache.CachingProviderImpl setzen.

Hinweis:

Alle JCache-Implementierungen unterstützen ein automatisches löschen von älteren / nicht genutzten Einträgen. Leider lässt sich die Morphium Policy nicht so ohne weiteres auf die Caches abbilden, weshalb Morphium ein eigenes Housekeeping implementiert, welches alte Elemente aus dem Cache entfernt.

  • Zusatzinfo*

die Cache Synchronisierung funktioniert auch mit der JCache kompatiblen Version. Und da dieser Cache auch außerhalb von Morphium genutzt werden kann, würden dann eben auch diese Caches synchronisiert werden.

Maven settings

    <groupId>de.caluga</groupId>
    <artifactId>morphium</artifactId>
    <version>3.2.2BETA1</version>

known bugs

Es gibt ein paar kleinere Bugs mit der aktuellen Beta:

  • die CacheListener Callbacks funktionieren wohl nicht mehr zu 100% mit JCache. Zumindest im Zusammenspiel mit EHCache scheint es da Probleme zu geben. Mit der Morphium eigenen JCache Implementierung funktioniert es
  • Es gibt einen Bug mit den Globalen Cache Einstellungen, welche nicht immer sauber an die Caches weiter gegeben werden.
  • Das Messaging Subsystem scheint auch betroffen zu sein.


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

MongoDB Messaging via Morphium

So, 06. 05. 2018 - Tags: java programming morphium

Ursprünglich veröffentlicht auf: https://caluga.de

Einer der vielen Vorteile von Morphium ist das integrierte Messagingsystem, welches z.B. auch für die Synchronisierung der Caches benutzt wird. Das funktioniert schon so seit mit einer der erste Versionen von Morphium.

Das Messaging setzt dabei auf ein ausgeklügeltes "Locking", damit Nachrichten, die nur für einen Empfänger bestimmt sind, auch nur dort ankommen. Leider kann man so etwas im Normalfall nur durch Polling, d.h. immer wieder die selbe Anfrage an die DB senden, realisieren. Aber seit der V3.2.0 wird im Falle der Verwendung eines Replicasets der OplogMonitor genutzt, um Messaging auf eine art "Push" aufzusetzen. D.h. die DB informiert die Clients, wenn es neue Nachrichten gibt.

Das reduziert die Last und erhöht die Reaktionszeit. Schauen wir uns das im Detail mal an...

Messaging mit Morphium - Funktionsweise

Wie einleitend schon erwähnt, muss man ab der V3.2.0 2 Fälle unterscheiden: ist Morphium mit einem ReplicaSet verbunden oder nicht.

Kein Replicaset => Polling

Das gitl im übrigen auch, für einen Sharded Cluster! D.h. wenn man mit dem MongoS verbunden ist, funktioniert das Messaging via Polling. Das Polling kann konfiguriert werden, d.h. wie oft pro Minute soll denn gefragt werden. Sollen die Nachrichten einzeln bearbeitet werden, oder alle auf ein mal etc.

Im Endeffekt läuft alles auf das Locking raus. Der Algorithmus sieht in etwa so aus (und kann in Messaging.java nachgelesen werden):

  1. ein Kommando an die Mongo senden, welches eine Nachricht die entweder direkt für dieses MessagingSystem oder für alle bestimmt ist und exklusiv (also nur ein mal bearbeitet werden darf) markiert ist, für dieses System lockt! Das geschieht mit Hilfe einer UUID die beim Start des Messaging erzeugt wird. Entweder eine oder alle passenden locken, je nach Einstellung.
  2. lesen ob es von mir (=dieses System) gelockte bzw. Nachrichten für alle gibt.
  3. jede dieser Nachrichten bearbeiten.
  4. die Nachricht als bearbeitet markieren (UUID->processed_by)
  5. kurz pausieren und zurück zu 1

Replicaset => OpLogMonitor bzw. ChangeStream

der OpLogMonitor ist ja schon eine Weile teil von Morphium. Mit dem OpLogMonitor wird ein TailableCursor, also ein stets geöffneter Curser, ans OpLog "gehängt". Damit bekommt man eine Nachricht, sobald eine Änderung im OpLog geschieht. Was im Replicaset immer dann der Fall ist, wenn es einen schreibzugriff auf die MongoDB gibt.

Ab Morphium 4.0 wird in diesem Fall auch der ChangeStream dieser Collection genutzt. Damit ist man nicht mehr auf den Zugriff des OpLog angewiesen.

Warum dann nicht einen TailableCursor direkt an die Msg-Collection hängen? Ja, das haben wir uns auch überlegt, leider funktioniert das aus folgenden Gründen nicht wirklich:

  1. tailableCursor funktionieren nur auf Capped Collections. Das ist zwar nicht tragisch in unserem Fall, aber etwas unschön
  2. man bekommt nur "neue" einträge übertragen, nicht jedoch updates. Das ist insbesondere für das Locking exklusiver Nachrichten nötig. Das würde dazu fürhren, dass man diesen Fall eh durch Polling lösen muss. Also kein Gewinn...

Die Verarbeitung der Messages ist im endeffect identisch zu oben, nur kann man sich das Locking vereinfachen. Bei einer eingehenden neuen Nachricht, passiert das:

  1. ist die eingehende Nachricht exklusiv markiert, findet das locking von oben statt aber nur für diese eine Nachricht (die ID hat man ja). Ist somit natürlich effizienter...
  2. ist die eingehende Nachricht nicht exklusiv markiert (und nicht von mir selbst), dann einfach bearbeiten
  3. ist die eingehende Nachricht exklusiv und direkt an mich adressiert, dann bearbeiten

im Falle von Updates einer Nachricht muss ja eigentlich erst mal gar nicht so viel passieren. Dennoch wird in diesem Fall die Nachricht noch mal kurz gelesen und geprüft, ob sie bearbeitet werden muss.

Messaging nutzen

Die Nutzung von Messaging in Morphium ist ziemlich einfach, man erstellt eine Instanz der Klasse Messaging und legt los:

   Messaging messaging=new Messaging(morphium, 500, true);
   messaging.start();

im Idealfall sollte man das Messaging system z.B. über Spring oder so initialisieren.

Nachricht senden

Und dann kann man eigentlich schon loslegen. Eine Nachricht wird wie folgt gesendet:

    messaging.storeMessage(new Msg("Testmessage", "A message", "the value - for now", 5000));

hier wird eine Nachricht mit einer TTL (time to live) von 5 sek gespeichert. Die Default TTL ist 30sek. Ältere Nachrichten werden automatisch gelöscht (aber nicht zwingend genau nach nach 30sek...)

Nachrichten sind per default broadcast, d.h. sie werden von allen verbundenen Clients gelesen und bearbeitet. Würde man die Nachricht auf "Exclusiv" setzen, könnten alle sie lesen, aber nur einer soll sie bearbeiten.

        Msg m = new Msg();
        m.setExclusive(true);
        m.setName("A message");

Diese Nachricht wird nur von einem einzigen empfänger bearbeitet!

Grundsätzlich werden Nachrichten übrigens vom Sender selbst nicht gelesen.

Und zu guter Letzt, man kann nachrichten natürlich auch direkt an einen Empfänger senden. Das passiert z.B. wenn Antworten gesendet werden. Die sollen ja nur vom Sender der ursprünglichen Nachricht bearbeitet werden.

Um eine Nachricht direkt an einen bestimmten Empfänger zu senden, muss man die ID des senders kennen. Das kann einfach durch eine eingehende Nachricht passieren oder man implementiert so eine Art discovery...

        Msg m = new Msg("testmsg1", "The message from M1", "Value");
        m.addRecipient(recipientId);
        m1.storeMessage(m);
storeMessage vs queueMessage

in den Integration-Tests in Morphium werden beide Methoden verwendet. Der Unterschied ist relativ einfach: storeMessage schreibt die Nachricht direkt in die Mongo wohingegen queueMessage asynchron funktioniert. D.h. die Nachricht wird erst entgegengenommen und im Hintergrund geschrieben. Evtl. der bessere Weg für Performance.

Nachrichten Emfpangen

Empfang von Nachrichten ist genauso einfach. im Messaging wird einfach ein MessageListener registriert:

           messaging.addMessageListener((messaging, message) -> {
            log.info("Got Message: " + message.toString());
            gotMessage = true;
            return null;
        });

Dabei ist message die Message und messaging das MessagingSystem. Der listener liefert hier als Ergebnis null zurück, könnte aber auch eine Nachricht als Antwort zurückgeben. Diese würde dann automatisch direkt an den Sender zurückgeschickt.

Mit Hilfe von messaging kann der listener auch auf das MessagingSystem zugreifen und bei Bedarf z.B. selbst Nachrichten versenden, die keine direkte Antwort sein sollten.

Des Weiteren kann der Listener die Bearbeitung "verweigern" indem er eine MessageRejectedException wirft. Der Sender kann auch darüber informiert werden, jedoch wird die Nachricht einfach wieder in den "Pool" zurückgeworfen und kann dann von anderen bearbeitet werden.

Das ist natürlich nur dann wirklich ein Problem, wenn es sich um eine exklusive Nachricht gehandelt hat. Denn dann sollte ein anderer Listener die Nachricht bearbeiten können.

Messaging im Einsatz - Synchronisierung der Caches

Innerhalb von Morphium setzt der CacheSynchronizer auf Messaging auf. Er benötigt im Constructor auch ein Messaging System.

Die Implementierun ist eigentlich recht einfach. Der CacheSynchronizer registriert sich als MorphiumStorageListener in Morphium und wird so über jeden Schreibzugriff informiert (denn nur dann muss ja auch der Cache synchronisiert werden).

public class CacheSynchronizer implements MessageListener, MorphiumStorageListener {

}

Kommt ein Schreibzugriff rein, wird geprüft ob es sich um ein gecachtes Entity handelt und wenn ja, wird eine ClearCache-Message über das Messaging gesendet. Diese beinhaltet, was zu tun ist abhängig von der gewählten Strategie (siehe auch Cache-Annotation in Morphium).

Außerdem bearbeitet der CacheListener natürlich auch eingehende Nachrichten. Kommt eine Nachricht an, wird einfach der in der Message beschriebene Cache gelöscht, das element geändert oder der Cache anderweitig angepasst.

Diese Clear-Nachrichten lassen sich auch über den CacheSynchronizer direkt versenden.

und es sollte auch nicht unerwähnt bleiben, dass man natürlich auch hier einen Listener registrieren kann, um sich über die Synchronisation der Caches informieren zu lassen.

Fazit

das Messaging feature von Morphium führt zu Unrecht ein Schattendasein. Es kann in vielen Fällen als einfacher Ersatz für full-blown MessagingSysteme dienen und kann super einfach eingesetzt werden. Mit der neuen Basis auf OpLogMonitor ist ein wichtiges neues Feature hinzugekommen um Messaging noch häufiger einsetzen zu können.


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

Neues Release Morphium 3.2.0Beta2 - Java Mongo Pojo Mapper

Mi, 02. 05. 2018 - Tags: morphium java mongodb mongo POJO

Ursprünglich veröffentlicht auf: https://caluga.de

Hi,

wir haben eine neue Beta-Version von Morphium V3.2.0 veröffentlicht. Diese beinhaltet einige kleinere bugfixes aber auch eine große Verbesserung: durch die Verwendung des Oplogmonitors wenn man mit einem Replicaset verbunden ist, kann das Messaging jetzt quasi auf Push-Messages der Datenbank reagieren. Damit ist kein Polling mehr nötig.

Das wird auch für die Synchronisierung der Caches genutzt.

Die Aktuelle Version kann von github runter geladen werden: https://github.com/sboesebeck/morphium

Die Beta V2 gibt es auch via maven central.

Das finale Release wird sicherlich bald verfügbar sein, also "stay tuned" emoji people:smirk


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

Neues Release Morphium V3.1.7

Di, 21. 11. 2017 - Tags: java mongo

Ursprünglich veröffentlicht auf: https://caluga.de

Wir haben gerade die Version V3.1.7 von Morphium - Caching Mongodb POJO-Mapper - released.

  • performance increase insert vs upsert
  • update handling of non-mongoid ID-fields (bugfix)
  • updated Tests
  • new strategy for buffered writer: WAIT
  • setting maxwait / timeout for waitstrategy in bufferedwriter
  • moving id creation to morphium, implementing proper inserts, fixing bugs
  • fixing buffered writing on sharded environments
  • performance increase
  • mongodb driver version update, checkfornew default fix

Details gibt es auf github. Einbidung erfolgt am einfachsten über Maven Central:

 <dependency>
            <groupId>de.caluga</groupId>
            <artifactId>morphium</artifactId>
            <version>3.1.7</version>
 </dependency>


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

Release Morphium 3.1.5

Fr, 29. 09. 2017 - Tags: morphium

Ursprünglich veröffentlicht auf: https://caluga.de

Es wurde gerade eine Neue Version 3.1.5 von Morphium released und sollte auch über Maven Central verfügbar sein:

  • bugfix bei den Statistiken
  • alle Treiber sind in ein eigenes Project verschoben
  • verbesserung beim speichern von binary data (byte arrays)
  • korrektur einiger Tests
  • checkForNew bei @CreationTime hat nun ein einleuchtenderes Verhalten: ist es global enabled kann es auch in der Annotation enabled werden. Ist es global disabled wird kein Check durchgeführt!

A


Kategorie: Allgemeines

Scrum oder nicht Scrum?

Mo, 14. 08. 2017 - Tags: scrum

... das ist nicht die Frage!

Denn genau das ist das Problem, viele verstehen den agilen Ansatz der Softwareentwicklung nicht wirklich. Ich würde das gerne mal ein wenig Beleuchten...

Softwareentwicklung früher

das vielleicht gute, auf jeden Fall aber alte Wasserfallmodell. Das sollte hinlänglich bekannt sein. Übertrieben formuliert teilt man ein Projekt in 2 Phasen ein - Konzeption und Implementierung. Das hat man sich von anderen Projekten so abgeguckt, wie z.B. dem Hausbau. Da gibt’s auch zuerst einen Plan und dann eben wird erst was getan.

Das hat auch seine Daseinsberechtigung in der Software: man konzeptioniert etwas komplett durch, macht einen Plan bis ins kleinste Detail, weiß schon vorher, wo alles hin muss und wie es laufen wird. Testet im Geiste alles zig mal durch und schon sind wir bei so rocksoliden Entwicklungsprozessen wie der der NASA. Deren Softwareentwicklung muss so gestrickt sein. Man kann ja nicht eben mal zum Mars fliegen und irgendwas am Marsrover austauschen, weil man bei der Konzeption etwas nicht bedacht hatte.

Agile Software Engineering

Aber eigentlich macht man doch heute dieses Agil, das ist doch der shit... da kann man die Produktivität vervierfachen, ohne dass man was tun muss. "Genau das machen wir ab jetzt..."

Und mit diesem Mindset geht man dann los und führt Agile Softwareentwicklung in der IT ein. Oder noch schlimmer, in der ganzen Firma.

Die Begeisterung für Scrum ist ja schön und gut. Aber es reicht leider nicht aus, sich für ein paar Wochen einen Coach in die Firma zu holen, ein Buch zu lesen und dann zu glauben, man kennt sich mit agilen Managementprozessen aus.

Das haut nicht hin. Und genau das habe ich jetzt schon mehrfach erlebt.

Scrum ist keine starre Vorgabe von Regeln oder Zwängen.

Scrum ist zunächst mal nur eine Menge von Methoden und Werkzeugen, die in der Praxis bei einigen Teams erfolgreich waren, d.h. deren Produktivität gesteigert haben.

Nur sollte man sich bei deren Geschichten auch mal ansehen, von wo aus die gestartet sind. Da ist eine Vervierfachung der Produktivität nicht unbedingt eine Herausforderung emoji people:smirk

Heutzutage arbeitet man in vielen Bereichen (wo sinnvoll) schon agil, ganz oft sogar, ohne es zu wissen. Für z.B. Supportabteilungen werden gerne Methoden aus der agilen Welt des Kanban herangezogen, weil das hilft, die eingehenden Anfragen bzw. deren Abarbeitung besser zu struturieren. Nur oft nennt man es weder agil noch Kanban, obwohl der gesamte Prozess in einem Support mehr oder minder agil sein muss

Also, wenn man da etwas verbessern will, sollte man sich genau ansehen, was das eigentliche Problem ist.

Das heißt auch nicht, dass das eigene Team alles davon umsetzen muss, was in den Scrum Guides und so beschrieben wird. Ich sehe es eigentlich so, dass man sich aus dem Scrum-Baukasten das raussucht, was für das eigene Team und die eigene Firma / Kultur gut funktioniert.

Das werden einige der Scrum-Nazis (so nenne ich gerne diejenigen, die Scrum buchstäblich und strikt machen) mich jetzt steinigen. Aber es ist so... Das passiert sogar bei den Erfindern. Ich habe meine PO-Zertifizierung bei Jeff Sutherland durchgeführt, nach eigenen Worten der Erfinder von Scrum.

Und in einer meiner letzten Firmen war ein Berater, der auch bei Jeff Sutherland gelernt hatte um Scrum einzuführen. Und dennoch hat er andere Werkzeuge benutzt und empfohlen, als dies Jeff getan hat. Das waren teilweise sogar widersprüchliche Aussagen (bezogen sich eben auf den konkreten Fall, unser Team in unserer Situation).

Und das ist gut so! Der Berater vor Ort kann viel besser entscheiden, was für das Team dort das richtige ist und eine entsprechende Empfehlung aussprechen. Jeff kann in solch einem Training ja nur allgemein reden und nur sehr oberflächlich auf Fragen eingehen.

Das zeigt aber, dass Scrum eben nicht immer überall gleich ist. Nichts ist in Stein gemeißelt. Pass es dir an, an dein Team, die aktuellen Umstände, die Firmenkultur. Wichtig ist, man sollte immer nur das nutzen von einer Methode, Vorgehensweise etc. bei der man sich auch wohl fühlt, die auch vom Team angenommen wird.

Was das passende ist, kommt auf unheimlich viele Faktoren an, die wichtigsten allerdings in wie weit das Management dem Team vertraut und extrem wichtig: auch umgekehrt!

Wenn das Management kein Vertrauen vom Team genießt, wird es nicht funktionieren (dann wird gar nichts funktioniere, aber Scrum insbesondere nicht).

Scrum und die Unternehmens / Teamkultur

Das Thema Vertrauen ist ein ganz wichtiges! Entgegen der früheren Methodiken wird bei Scrum die "Verantwortung" bei der Softwareentwicklung "umgedreht". D.h. es gibt kein Projektmanager mehr vor, was man wann wie zu implementieren hat, sondern es läuft umgekehrt. Das Team entscheidet, was es im nächsten Sprint machen will und wird auch daran gemessen.

Wenn allerdings die Chefetage kein Vertrauen darin hat, dass das in die richtige Richtung laufen wird, wird das zu Konflikten kommen. Und nach meiner Erfahrung endet das in einen "quasi"-Wasserfall.

Das team darf zwar vordergründig entscheiden, aber wenn die Entscheidung sich nicht mit der Der Geschäftsleitung deckt, wird so lange rumdiskutiert, bis sie das tut.

Beispiel:

im Refinement ist der CEO zugegen (eigentlich schon falsch, da sein PO das übernehmen sollte), das Team hat eine Userstory in Bearbeitung, sagen wir eine Architektonische Entscheidung ist teil der Story. Man kann sich zwischen A, B und C entscheiden. Das team entscheidet sich für A.

Der CEO kommentiert diese Entscheidung mit etwas wie "hmmm.... ja, schon gut, aber vielleicht überlegen wir noch mal weiter"

Das Team empfindet B als noch gangbaren Weg, aber C auf keinen Fall. C ist aber der Favorisierte Weg des CEO.

Was dann passiert ist, dass der CEO immer wieder sowas sagt wie "Ja, cool, aber wie wäre es wenn wir weiter überlegen"

Wenn dann irgendwer sagt "dann bleibt ja nur C übrig" springt der CEO drauf an und sagt "cool, wir machen C"

und wenn es später knallt kommt "Aber ihr wolltet doch C machen".

Klingt an den Haaren herbei gezogen? Nein, das ist so ähnlich schon passiert und ich war dabei! Klar, ist das hier überspitzt, aber im Grunde lief es genau so ab.

Wo kommt so was her? Fehlendes Vertrauen, kein Loslassen! Und zu schwache Scrum Master! Damit ist nicht die Person gemeint, sondern die Rolle im Team bzw. der Firma. Der Scrum Master durfte halt nicht das, was ein Scrum master eigentlich so machen sollte, was ihn in seiner Rolle geschwächt hat.

Das war in meinem Fall dann auch noch gepaart mit einer sehr komischen Fehlerkultur. Fehler wurden immer wieder durchgekaut und aufs Tablett gebracht, als Beispiele um Entscheidungen zu beeinflussen. Aber es wurde sehr häufig erwähnt, dass wir doch eine ach so tolle Fehlerkultur hätten und alles doch vergeben und vergessen wird - "Aber...."

Das führt dann dazu, dass das Team auch gar keine Lust mehr hat, irgendwas zu entscheiden. Die Entscheidung wird einem abgenommen - fein. Schuld ist man am Ende so oder so...

Und damit funktioniert agilität so gut wie gar nicht mehr. Damit hat man zwar von Außen das etikett "wir sind agil" aber innen drin ist es eigentlich ein Unding aus Wasserfall und... ja, was eigentlich? Monarichie?

Bei Scrum geht es um Transparenz

Viele Dinge, die man in Scrum (oder allgemeinen agilen Methoden) tut, versuchen in alle möglichen Richtungen Transparenz zu schaffen. Das soll heißen, man versucht Fehler schnell und klar sichtbar zu machen. Defizite aufzuzeigen, Verbesserungspotentiale zu erkennen. Das oben genannte Beispiel war klar, wurde oft genug in der Retro angesprochen. Aber der Scrum Master kam nicht gegen die Geschäftsleitung an, weshalb sich auch nichts geändert hat und irgendwann hat man es "akzeptiert" - in der Retro kam es nicht mehr vor.

Aber natürlich will man auch die normale Arbeit durch Transparenz und klare Kommunikationsstukturen zu verbessern. Wenn jeder weiß, wohin die Reise geht, tut er sich deutlich leichter.

Und genau da ist die Crux: viele Führungskräfte fühlen sich nicht wohl bei dem Gedanken, das Steuer loszulassen, das Team mal machen zu lassen. Das führt schnell zu Konflikten und dazu, dass das Team dem Management nicht mehr vertraut.

Auch ist Scrum und diese Transparenz nicht für jeden im engineering team das richtige. Denn wenn das Team Verantwortung übernimmt, muss es dafür auch einstehen. Und das will nicht jeder. Da ist das gute Wasserfallmodell "schöner". Dort hat man auch keine Entwickler im Einsatz, sondern Programmierer emoji people:smirk - da wird einfach nur runtergeschrieben, was im Konzept steht (im schlimmsten Fall). Ich weiß, das ist übertrieben dargestellt. Einige kommen mit der Arbeit nach Feinkonzepten einfach besser klar und das ist auch ok so.

"Helden"

Gerade in der Softwareentwicklung gibt es gerne "Leads", die zwar mit ihrem Team nach außen hin agil arbeiten (d.h. sie sitzen in allen Scrum Meetings, haben irgendwo ne WBC hängen), aber agieren nicht nach Scrum. Denn diese erfahrensten Mitarbeiter machen alles und geben wenig ab, sie sind die "Helden" die alles allein schaffen. Das führt dann zu "HiWi-Teams mit einem Head". Das frustriert die alle, da sich keiner weiterentwickeln kann und das Team nur so schnell sein kann, wie dieser Lead - Skalierung Fehlanzeige!

Auch für den Lead ist das eigentlich gar keine so tolle Situation. Da er mehr oder minder alles allein machen muss, häufen sich bei ihm die Überstunden, während die anderen in der Nase bohren. Dennoch kommt man aus diesem "Heldentum" nur schwer wieder raus, da es sich gut anfühlt, der "Held" zu sein. Denn "Ich habe Feature XY implementiert" - "ich hab den Bug bla behoben" - "Dank mir läuft wieder alles"...

Scrum sollte so was eigentlich aufzueigen, aber in solchen Situationen wird eben die Flexibilität von Scrum zum Problem: die Dinge, die dieses Ungleichgewicht aufzeigen würden (wie z.B. strukturierte Scrum Boards in denen erkannt wird, wer was tut, Tandeming etc) werden nicht eingesetzt.

Agile Vorgehensweisen helfen aber auch in so einem Fall, zumindest sollte es transparent werden. Aber dafür muss eben alles zusammenpassen....

Scrum im Rest der Firma

Agilität funktioniert sehr gut innerhalb der IT-Teams selbst, das ist eigentlich etwas, was jedem Softwareentwickler entgegen kommt, da er - wenn er ein eigenes Projekt aufsetzt - ganz genauso arbeiten würde. Man würde immer wieder Tasks abarbeiten, schauen, wie funktioniert es und sich nach und nach an eine optimale Lösung herantasten. Aber wie funktioniert die Schnittstelle zu anderen Teams?

Und das ist das Problem. Ich habe es erlebt, dass die Schnittstelle sehr gut funktioniert, obwohl der gesamte Rest der Firma nicht agil ausgerichtet ist. Das war z.B. bei einem Consultingunternehmen der Fall. Dort war es notwendig, für die Angebote und Ausschreibungen Konzepte zu liefern, die dann verfeinert wurden. Aber die Implementierung lief dann agil.

Das hat erstaunlich gut funktioniert, obwohl ein methodischer Bruch vorlag.

Natürlich geht das nicht immer so. Wenn das Management Reports nach Wasserfall erwartet, das Team aber agil arbeitet, ist es schwer das zusammenzubringen.

Das habe ich bei einer Firma erlebt, die versucht hat testweise mal ein Projekt agil durchzuführen. Mit dem Effekt, dass es überhaupt nicht funktioniert hat. Die Kommunikation lief völlig daneben, die Erwartungshaltungen waren komplett unterschiedlich und am Schluss gab es nur noch eine Menge von Lessons-Learned-Meetings und der Versuch den Gang vor Gericht zu vermeiden.

Schlimm ist es auch, wenn das Management versucht Scrum zu sein, selbst aber keine Ahnung davon hat. Das ist wirklich das schlimmste, was passieren kann.

Das Management vergewaltigt dann sehr häufig bestimmte Scrum-Zeremonien um z.B. ein Report-Meeting zu bekommen, obwohl eigentlich ein Scum-of-Scrums angesetzt war. (erkennt man meistens daran, dass weder die Zeiten eingehalten werden, noch das erwartete Ergebnis entsteht und man einen Status über alle Projekte abgeben muss).

Sowas kann nur dann funktionieren, wenn das Management Scrum wirklich verinnerlicht hat und schon viel Erfahrung damit hat und/oder der Scrummaster ein sehr erfahrener ist, mit einem Rückgrat aus Stahl und einem unerschütterlichen Selbstbewusstsein. Es schadet auch nicht, geduldig zu sein....

Das ganze endet nämlich in vielen Konflikten: der CEO der Scrum so zu 50% verstanden hat und immerhin schon 3 Monate Erfahrung sammeln konnte (weil in seiner Firma eines der Teams Scrum macht), und dem Scrum Master, der seine Felle davon schwimmen sieht, weil seine ganze Meetingstruktur kaputt geht da die Zeremonien leider misbraucht werden und er also gegen den CEO bestehen muss.

Das ist tough und ich kenne bisher keine Firma in der das auch nur Ansatzweise funktioniert. Mal abgesehen davon, dass es nicht wirklich Sinn macht.

Agile Methoden sind fein, insbesondere im Bereich der Softwareentwicklung oder wenn man eben etwas "erschaffen" will, von dem man bei Beginn noch nicht genau weiß, was es sein wird.

Ähm... wenn mein Management in der Firma so an das ganze herangeht, bekomme ich ein wenig angst. Sie wissen noch gar nicht wo es hingeht und wollen sich herantasten?

gruselige Vorstellung. Und genau deswegen sollte man sich als Fürhrungskraft auch mal mit dem Thema Agile Management beschäftigen. Das ist nämlich nicht Scrum sondern eine Sammlung von Werkzeugen, die einem Helfen in einer Agilen Welt auch im Mangement zu bestehen.

Links:

http://billschofield.net/The-Insufficiency-of-Scrum/

https://www.linkedin.com/pulse/scrum-makes-you-dumb-daniel-jones?trk=v-feed&lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3BhpLGl29RKJkQ2jmK4ZbMIg%3D%3D


Kategorie: Computer

Neue Blogging Software

Di, 16. 05. 2017 - Tags: java jblog security

Ursprünglich veröffentlicht auf: https://boesebeck.name

ich habe mich ja schon so einige Male über Wordpress beschwert (z.B. hier). Und das habe ich zum Anlass genommen, mal meine Software-Development-Skills zusammenzukratzen und übers Wochenende eine neue Blogging-Software zusammenzustopseln.

Raus gekommen ist diese wunderschöne (naja...ich finds ok) Seite hier. Aber zunächst mal wie es dazu kam...

PHP Sucks

naja.. nicht wieder gleich losheulen. Ich mag PHP nicht besonders, weil ich es nicht gut kenne. Und deswegen ist mir Wordpress auch ein Mysterium. Die Konfiguration ist mehr oder minder "gut Glück" und PHP an sich ist ja schon schwierig, sicher hin zu bekommen.

Dummerweise wurde mein Blog auch mehrmals gehackt und das hat mich schon genervt. Also, wollte ich was mit Java machen, aber leider gibt’s da keine echte gute / einfache Bloggingsoftware.

warum also nicht selbst machen?

Dachte ich so bei mir. So schwer kann das ja nicht sein. Also wollte ich mal ein Blog schreiben, das mit einem

  • einfachen Technology stack auskommt
  • keine komplexe Plugin-Technologie braucht, wenn es Funktionalität braucht, schreib ich die selbst emoji people:smile
  • themes oder desings... öhm... ja, das könnte besser sein, aber ich denke, das ist ok so.
  • Security... ja das leidige Thema. Ich hab die Blogging-Software (ich hab sie nicht sehr kreativ jblog getauft) ja selbst geschrieben. Eigentlich sollte es einigermassen passen, aber wer weiß das schon. Sicher ist nur: die Einfachen exploits für PHP und / oder wordpress laufen nicht mehr! Ätsch!
  • Internationalisierung war auch so ein Thema. Mit Wordpress nicht so einfach machbar. in Jblog ziemlich simpel: es kann Deutsch und Englisch. Mehr sprachen, spreche ich auch nicht, insofern ist das ok.
  • Whitelabeling: Ich hatte ja noch so ein paar andere Domains rumfliegen: boesebeck.biz aus der Zeit als ich selbständig war, caluga.de unter der meine ganzen opensource-projekte laufen und eben diese hier. Ich dachte mir, ich könnte das blog aufteilen und so die ganzen "alten" domains auch wieder zum leben erwecken.
  • eine Admin-Oberfläche: es sind jetzt zwar 3 blogs, aber ich hab keine lust, alles 3x zu administrieren. Alle nutzen die selbe DB, alle werden gleich administriert. Zeigen halt nur was anders an und sehen twas anders aus.

Jblog

ich bin ganz zufrieden damit, ehrlich gesagt und endlich sind wir live! Es hat dann doch etwas länger gedauert als ein Wochenende, aber so richtig lange dann auch nicht. ging doch erstaunlich flott...

Aber wenn jetzt so einige Links nicht mehr sauber funktionieren, oder Bilder nicht richtig angezeigt werden, seht es mir nach - ich werde das alles nachkorrigieren.

die verschiedenen Blogs

Boesebeck.name - dieses Blog hier

Das wird mein privates hauptblog sein. Da kommt der ganze hobbykram rein, Drohnentechnik, Games, Gadgets etc.

Caluga.de - das Java blog

ich möchte, da mein ganzes Opensource Zeuch, wie z.B. morphium auch dort beheimatet ist, den ganzen PRogrammierung / Java / sonstige Sprachen ja, wo ich das jetzt so schreibe, ist Java Blog wohl nicht ganz so passend

boesebeck.biz

Naja, dem Namen nach sollte es ja eh eine business Site sein, also will ich da so Dinge aus der Arbeitswelt posten. Z.B. Scrum, Vorgehensweisen etc. Mal sehen, was mir da so einfallen wird.

Updates

Ja, das wird, wie üblich, spannend werden, denn ich habe auch nicht so viel Zeit, jeden Tag neuen Content zu produzieren. Ich werde aber versuchen, jedes Blog regelmäßig mit ein wenig Content zu versorgen... mal sehen, in wie weit das klappt...

technisches

Wie oben schon erwähnt, der ganze Technikkram kommt dann auf caluga.de emoji people:smirk


Kategorie: Computer

Stephans Blog wieder online...

Fr, 12. 06. 2015 - Tags: allgemein blog

Ursprünglich veröffentlicht auf: https://boesebeck.name

Das war stressig. Zum Umzug kam noch hinzu, dass mein Server die Grätsche gemacht hat. Ich musste neu installieren. Was ja – dank Backups – eigentlich kein allzu großer Aufwand wäre, hätte ich nicht vergessen, ein Backup von der Datenbank zu machen… Deswegen jetzt der neue Start des alten Blogs ;-)


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

Morphium Doku V3.0

Fr, 05. 09. 2014 - Tags: morphium java mongo

Ursprünglich veröffentlicht auf: https://caluga.de

There is no German Version available unfortunately - want help translating / documenting? Conctact us on github or via slack

Morphium Documentation

This documentation is refering to Morphium version [%morphium_version] and mongodb [%mongodb_version]. this documentation follows "MultiMarkdown" and was created using the MultiMarkdownComposer.

HTML Version here: MorphiumDoku If you just want to start right now, read [quick start]!

Ideas and concepts

When we started using MongoDB there was no fully capable POJO Mapper available. The only thing that was close to useable was Morphia (which is now developed by MongoDb. Unfortunately, Morphia had some issues, and lacked some features, we'd like to have, like (besides the usual features fast mapping, reliable query interface and so on):

  • Thread safety
  • cluster awareness
  • declarative caching
  • profiling support
  • support for partial updates
  • reference support incl. lazy loading of references
  • adaptable API (need to implement special POJO Mappings, Cache implementation change etc)
  • Cache synchronization in cluster
  • Validation
  • Declarative Index specification
  • Aggregation support

At that time there was nothing available providing all those features or what we could use as a basis to create those features (although we tried to implement that on base of Morphia - but the architecture of Morphia was not built for customization).

So, we started creating our own implementation and called it "Morphium" to honor the project "Morphia" which was the best around at that time.

But Morphium is a complete new Project, it was built totally from scratch. Even the POJO-Mapper is our own development (although there were some available at that point), but we had some special needs for Morphium's mapping.

The mapping takes place on a per-type basis. That means, usually (unless configured otherwise) the data of all objects of a certain type, will be stored in a corresponding collection.

In addition to that, the mapping is aware of object hierarchy and would even take annotations and settings into account, that are inherited.

Usually Morphium replaces camel case by underscore-separated strings. So an Object of type MyEntity would be stored in the collection my_entity. This behaviour can be configured as liked, you could even store all Objects in one collection. (see [Polymorphism])

Changes in Version 3.0

Motivation

Morphium 3.0 brings a lot improvements and changes, most of them are not really visible to the user, but unfortunately some of them make V3.x incompatible to V2.x.

The changes were triggered by the recent mongodb java driver update to also 3.0, which brings a whole new API. This API is (unfortunately also) not backward compatible[^not quite true, the driver contains both versions actually, but old API is usually marked deprecated]. This made it hard to add the changes in the official driver into morphium. Some of the changes made it also impossible to implement some features in morphium as it was before. So - the current implementation of morphium uses both old and new API - wich will break eventually.

The next step was, to be more independent from the driver, as those changes caused problems almost throughout the whole code of morphium. So, introducing with V3.0 of morphium, the driver is encapsulated deep within morphium.

Unfortunately, even the basic document representation changed[^old version used BasicDBObject, new version uses Document], which are very similar, but unfortunately represented in a whole new implementation of BSON[^binary json - details can be found here].

Also, we had some problems with dependencies in maven, causing to be several version of the mongodb driver being installed on production - which then caused some weird effects, most of them not really good ones ;-)

This made us reduce all dependency to the mongodb driver to a minimum - actually it is only used in the MorphiumDriver implementation for the official mongodb driver. But that also meant, we needed to get rid of all usages of ObjectID and BasicDBDocument and reduce usages of that into the driver implementation within morphium.

The question was - do we need to introduces some new object type for representing a Map<String,Object>? We thought no, so we changed the whole code in morphium, to internally use only standard Java8 API.

Yes, that is one feature also, since Morphium 3.0 we‘re running on java 8.

Changes

As you know the motivation now, these are the changes.

  • Driver encapsulated and configurable - you can now implement your own driver for usage with morphium
  • no usage of MongoDb classes, replaced by type MorphiumId and simple Map<String,Object> - this might actually break your code!
  • (soon) MongoDB Dependency in maven will be set to be provided, so that you can decide, which Version of the driver you want to use (or none...)
  • Morphium 3.0 includes some own implementation of drivers (mainly for testing purpose):
    • Driver: This is the Implementation of MorphiumDriver using the official Mongodb driver (V3.x)
    • InMemoryDriver: Not connecting to any mongo instance, just storing into memory. Good for testing. Does not support Aggregation!
    • SingleConnectDirectDriver: Just connecting to a master node, no failover. Useful if you do not have a replicaset
    • SingleConnectThreaddedDriver: Same as above, but uses a thread for reading the answers - slightly better performance in multithreaded environments, but only useful if you don't run a replicaSet
    • MetaDriver: A full featured implementation of the MorphiumDriver Interface, can be used as replacement for the mondogdb driver implementation. It uses a pool of SingleConnectThreaddedDriver to connect to mongodb.
  • Many changes in the internals
  • in references you can now specify the collection the reference should point to.
  • improvements in the internal caches, using the new improved features and performance of Java8[^see also here]
  • complete rewrite of the bulk operation handling
  • code improvements on many places, including some public interfaces (might break your code!)

quick start

Simple example on how to use Morphium:

First you need to create data to be stored in Mongo. This should be some simple class like this one here:

    @Entity
    public class MyEntity {
        @Id
        private MorphiumId myId;
        private int aField;
        private String other;
        private long property;
        //....  getter & setter here
    }

This given entity has a couple of fields which will be stored in Mongo according to their names. Usually the collection name is also derived from the ClassName (as most things in Morphium, that can be changed).

The names are usually translated from camel case (like aField) into lowercase with underscores (like a_field). This is the default behavior, but can be changed according to your needs.

In mongo the corresponding object would be stored in a collection named my_entity and would look like this:

    {
      _id: ObjectId("53ce59864882233112aa018df"),
      a_field: 123,
      other: "value"
    }

By default, null values are not serialized to mongo. So in this example, there is no field "property".

The next example shows how to store and access data from mongo:

    //creating connection 
    MorphiumConfig cfg=new MorphiumConfig()
    cfg.setHostSeed("localhost:27018", "mongo1","mongo3.home")
    //connect to a replicaset 
    //if you want to connect to a shared environment, you'd add the addresses of 
    //the mongos-servers here 
    //you can also specify only one of those nodes, 
    //Morphium (or better: mongodb driver) will figure out the others
    //connect 
    Morphium morphium=new Morphium(cfg);
    
    //Create an entity 
    MyEntity ent=new MyEntity()
    ent.setAField(123)
    ent.setOther("value")
    ent.setProperty(122l)
    morphium.store(ent);
    
    //the query object is used to access mongo 
    Query q=morphium.createQueryFor(MyEntity.class)
    q=q.f("a_field").eq(123)
    q=q.f("other").eq("value")
    q=q.f("property").lt(123).f("property").gt(100);
    
    List lst=q.asList();
    
    //or use iterator 
    for (MyEntity e:q.asIterable(100,2)) { 
        // iterate in windows of 100 objects 
        // 2 windows lookAhead 
    }

This gives a short glance of how Morphium works and how it can be used. But Morphium is capable of many more things...

Architecture

Morphium is built to be very flexible and can be used in almost any environment. So the architecture needs to be flexible and sustainable at the same time. Hence it's possible to use your own implementation for the cache if you want to.

There are four major components of Morphium:

  1. the Morphium Instance: This is you main entrypoint for interaction with Mongo. Here you create Queries and you write data to mongo. All writes will then be forwarded to the configured Writer implementation, all reads are handled by the Query-Object
  2. Query-Object: you need a query object to do reads from mongo. This is usually created by using Morphium.createQueryFor(Class<T> cls). With a Query, you can easily get data from database or have some things changed (update) and alike.
  3. the Cache: For every request that should be sent to mongo, Morphium checks first, whether this collection is to be cached and if there is already a batch being stored for the corresponding request.
  4. The Writers: there are 3 different types of writers in Morphium: The Default Writer (MorphiumWriter) - writes directly to database, waiting for the response, the BufferedWriter (BufferedWriter) - does not write directly. All writes are stored in a buffer which is then processed as a bulk. The last type of writer ist the asynchronous writer (AsyncWriter) which is similar to the buffered one, but starts writing immediately - only asynchronous. Morphium decides which writer to use depending on the configuration an the annotations of the given Entities. But you can always use asynchronous calls just by adding aAsyncCallback implementation to your request.

Simple rule when using Morphium: You want to read -> Use the Query-Object. You want to write: Use the Morphium Object.

There are some additional features built upon this architecture:

  • messaging: Morphium has a own messaging system.
  • cache synchronization: Synchronize caches in a clustered environment. Uses messaging
  • custom mappers - you can tell Morphium how to map a certain type from and to mongodb. For example there is a "custom" mapper implementation for mapping BigInteger instances to mongodb.
  • every of those implementations can be changed: it is possible to set the class name for the BufferedWriter to a custom built one (in MorphiumConfig). Also you could replace the object mapper with your own implementation by implementing the ObjectMapper interface and telling morphium which class to use instead. In short, these things can be changed in morphium / morphiumconfig:
    • MorphiumCache
    • ObjectMapper
    • Query
    • Field
    • QueryFactory
    • Driver (> V3.0)
  • Object Mapping from and to Strings (using the object mapper)

Configuring Morphium

First lets have a look on how to configure Morphium. As you already saw in the example in the last chapter, the configuration of Morphium ist encapsulated in one Object of type MorphiumConfig. This object has set some reasonable defaults for all settings. So it should be just as described above to use it.

Configuration Options

There are a lot of settings and customizations you can do within Morphium. Here we discuss all of them:

  • loggingConfigFile: can be set, if you want Morphium to configure your log4j for you. Morphium itself has a dependency to log4j (see Dependencies).
  • camelCaseConversion: if set to false, the names of your entities (classes) and fields won't be converted from camelcase to underscore separated strings. Default is true (convert to camelcase)
  • maxConnections: Maximum Number of connections to be built to mongo, default is 10
  • houseKeepingTimeout: the timeout in ms between cache housekeeping runs. Defaults to 5sec
  • globalCacheValidTime: how long are Cache entries valid by default in ms. Defaults to 5sek
  • writeCacheTimeout: how long to pause between buffered writes in ms. Defaults to 5sek
  • database: Name of the Database to connect to.
  • connectionTimeout: Set a value here (in ms) to specify how long to wait for a connection to mongo to be established. Defaults to 0 (⇒ infinite)
  • socketTimeout: how long to wait for sockets to be established, defaults to 0 as well
  • socketKeepAlive: if true, use TCP-Keepalive for the connection. Defaults to true
  • safeMode: Use the safe mode of mongo when set to true
  • globalFsync, globalJ: set fsync (file system sync) and j (journal) options. See mongo.org for more information
  • checkForNew: This is something interesting related to the creation of ids. Usually Ids in mongo are of type ObjectId. Anytime you write an object with an _id of that type, the document is either updated or inserted, depending on whether or not the ID is available or not. If it is inserted, the newly created ObjectId is being returned and add to the corresponding object. But if the id is not of type ObjectId, this mechanism will fail, no objectId is being created. This is no problem when it comes to new creation of objects, but with updates you might not be sure, that the object actually is new or not. If this obtion is set to true Morphium will check upon storing, whether or not the object to be stored is already available in database and would update. Attention: Morphium 3.0 removed the dependency from mogodb.org codebase and hence there is no ObjectId for POJOs anymore. You should replace these with the new MorphiumId.
  • writeTimeout: this timeout determines how long to wait until a write to mongo has to be finshed. Default is 0⇒ no timeout
  • maximumRetriesBufferedWriter: When writing buffered, how often should retry to write the data until an exception is thrown. Default is 10
  • retryWaitTimeBufferedWriter: Time to wait between retries
  • maximumRetriesWriter, maximumRetriesAsyncWriter: same as maximumRetriesBufferedWriter, but for direct storage or asynchronous store operation.
  • retryWaitTimeWriter, retryWaitTimeAsyncWriter: similar to retryWaitTimeBufferedWriter, but for the according writing type
  • globalW: W sets the number of nodes to have finished the write operation (according to your safe and j / fsync settings)
  • maxWaitTime: Sets the maximum time that a thread will block waiting for a connection.
  • writeBufferTime: Timeout for buffered writes. Default is 0
  • autoReconnect: if set to true connections are re-established, when lost. Default is true
  • maxAutoReconnectTime: how long to try to reconnect (in ms). Default is 0⇒ try as long as it takes
  • blockingThreadsMultiplier: There is a max number of connections to mongo, this factor determines the maximum number of threads that may be waiting for some connection. If this threshold is reached, new threads will get an Exception upon access to mongo.
  • mongoLogin,mongoPassword: User Credentials to connect to mongodb. Can be null.
  • mongoAdminUser, mongoAdminPwd: Credentials to do admin tasks, like get the replicaset status. If not set, use mongoLogin instead.
  • acceptableLatencyDifference: Latency between replicaset members still acceptable for reads.
  • autoValuesEnabled: Morphium supports automatic values being set to your POJO. These are configured by annotations (@LasChange, @CreationTime, @LastAccess, ...). If you want to switch this off globally, you can set it in the config. Very useful for test environments, which should not temper with productional data
  • readCacheEnabled: Globally disable readcache. This only affects entities with a @Cache annotation. By default it's enabled.
  • asyncWritesEnabled: Globally disable async writes. This only affects entities with a @AsyncWritesannotation
  • bufferedWritesEnabled: Globally disable buffered writes. This only affects entities with a @WriteBuffer annotation
  • defaultReadPreference: whether to read from primary, secondary or nearest by default. Can be defined with the @ReadPreference annotation for each entity.
  • replicaSetMonitoringTimeout: time interval to update replicaset status.
  • retriesOnNetworkError: if you happen to have an unreliable network, maybe you want to retry writes / reads upon network error. This settings sets the number of retries for that case.
  • sleepBetweenNetworkErrorRetries: set the time to wait between network error retries.
  • blockingThreadsMultiplier: Sets the multiplier for number of threads allowed to block waiting for a connection.

In addition to those settings describing the behavior of Morphium, you can also define custom classes to be used internally:

  • omClass: here you specify the class, that should be used for mapping POJOs (your entities) to DBOject. By Default it uses the ObjectMapperImpl. Your custom implementation must implement the interface ObjectMapper.
  • iteratorClass: set the Iterator implementation to use. By default MorphiumIteratorImplis being used. Your custom implementation must implement the interface MorphiumIterator
  • aggregatorClass: this is Morphium's representation of the aggregator framework. This can be replaced by a custom implementation if needed. Implements Aggregator interface
  • queryClass and fieldImplClass: this is used for Queries. If you want to take control over how queries ar built in Morphium and on how fields within queries are represented, you can replace those two with your custom implementation.
  • cache: Set your own implementation of the cache. It needs to implement the MorphiumCache interface. Default is MorphiumCacheImpl. You need to specify a fully configured cache object here, not only a class object.
  • driverClass: Set the driver implementation, you want to use. This is a string, set the class name here. E.g. morphiumconfig.setDriverClass(MetaDriver.class.getName()

Morphium Config Directly

The most straight foreward way of configuring Morphium is, using the object directly. This means you just call the getters and setters according to the given variable names above (like setMaxAutoReconnectTime()).

The minimum configuration is explained above: you only need to specify the database name and the host(s) to connect to. All other settings have sensible defaults, which should work for most cases.

Morphium Config From Property File

the configuration can be stored and read from a property object.

MorphiumConfig.fromProperties(Properties p); Call this method to set all values according to the given properties. You also can pass the properties to the constructor to have it configured.

To get the properties for the current configuration, just call asProperties() on a configured MorphiumConfig Object.

Here is an example property-file:

maxWaitTime=1000
maximumRetriesBufferedWriter=1
maxConnections=100
retryWaitTimeAsyncWriter=100
maxAutoReconnectTime=5000
blockingThreadsMultiplier=100
housekeepingTimeout=5000
hostSeed=localhost\:27017, localhost\:27018, localhost\:27019
retryWaitTimeWriter=1000
globalCacheValidTime=50000
loggingConfigFile=file\:/Users/stephan/morphium/target/classes/morphium-log4j-test.xml
writeCacheTimeout=100
connectionTimeout=1000
database=morphium_test
maximumRetriesAsyncWriter=1
maximumRetriesWriter=1
retryWaitTimeBufferedWriter=1000

The minimal property file would define only hosts and database. All other values would be defaulted.

If you want to specify classes in the config (like the Query Implementation), you neeed to specify the full qualified class name, e.g. de.caluga.morphium.customquery.QueryImpl

Morphium Config From Json File

The standard toString()method of MorphiumConfig creates an Json String representation of the configuration. to set all configuration options from a json string, just call createFromJson.

Documentation

Singleton Access

In some cases it's more convenient to use a singleton Instance to access Morphium. You don't need to implement a thread safe Morphium Singleton yourself, as Morphium does already have one.

The MorphiumSingleton is configured similar to the normal Morphium instance. Just set the config and you're good to go.

    MorphiumConfig config=new MorphiumConfig()//..configure it here
    MorphiumSingleton.setConfig(config);
    MorphiumSingleton.get().createQueryFor(MyEntity.class).f(...)

Connection to mongo and initializing of Morphium is done at the first call of get.

POJO Mapping

When talking about POJO Mapping, we're saying we marshall a POJO into a mongodb representation or we unmarshall the mongodb representation into a POJO.

Marshaling and unmarshalling is of utter importance for the functionality. It needs to take care of following things:

  • un/marshall every field. Easy if it’s a primitive datatype. Map to corresponding type in Monogo - mostly done by the mongodb java driver (or since 3.0 the MorphiumDriver implementation)
  • when it comes to lists and maps, examine every value. Maps may only have strings as keys (mongoldb limitation), un/marshall values
  • when a field contains a reference to another entity, take that into account. either store the
  • the POJO transformation needs to be 100% thread safe (Morphium itself is heavily multithreaded)

The ObjectMapper is the core of Morphium. It's used to convert every entity you want to store into a mongoldb document (java representation is a DBObject). Although it's one of the key things in Morphium it's still possible to make use of your own implementation (see chapter [Configuring Morphium]).

Querying Mongo

This is done by using the Query object. You need to create one for every entity you want to issue a query for. You could create one yourself, but the easiest way of doing so is calling the method .createQueryFor(Class class) in Morphium.

After that querying is very fluent. You add one option at a time, by default all conditions are AND-associated:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.f("a_field").eq("Value");
    q=q.f("counter").lt(10);
    q=q.f("name").ne("Stephan").f("zip").eq("1234");

The f method stands for "field" and returns a Morphium internal representation of mongo fields. Threre you can call the operators, in our case it eq for equals, lt for less then and ne not equal. There are a lot more operators you might use, all those are defined in the MongoField interface:

    public Query all(List
    public Query eq(Object val);
    public Query ne(Object val);
    public Query size(int val);
    public Query lt(Object val);
    public Query lte(Object val);
    public Query gt(Object val);
    public Query gte(Object val);
    public Query exists();
    public Query notExists();
    public Query mod(int base, int val);
    public Query matches(Pattern p);
    public Query matches(String ptrn);
    public Query type(MongoType t);
    public Query in(Collection vals);
    public Query nin(Collection vals);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     * spherical distance calculation
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query nearSphere(double x, double y);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query near(double x, double y);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     * spherical distance calculation
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query nearSphere(double x, double y, double maxDistance);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query near(double x, double y, double maxDistance);

span class='java-comment'> /**
     * search for entries with geo coordinates wihtin the given rectancle - x,y upper left, x2,y2 lower right corner
span class='java-comment'> */
    public Query box(double x, double y, double x2, double y2);
    public Query polygon(double... p);
    public Query center(double x, double y, double r);
    
span class='java-comment'> /**
     * same as center() but uses spherical geometry for distance calc.
     *
     * @param x - pos x
     * @param y - y pos
     * @param r - radius
     * @return the query
span class='java-comment'> */
    public Query centerSphere(double x, double y, double r);
    
    public Query getQuery();
    public void setQuery(Query q);
    public ObjectMapper getMapper();
    public void setMapper(ObjectMapper mapper);
    public String getFieldString()
    public void setFieldString(String fld);

Query definitions can be in one line, or as above in several lines. Actually the current query object is changed with every call of f...something combination. The current object is always returned, for making the code more legible and understandable, you should assign the query as shown above. This makes clear: "The object changed"

If you need an "empty" query of the same type, you can call the method q. This method will return an empty query of the same type, using the same mapper etc. But only without conditions or something - just plain empty.

As already mentioned, the query by default creates AND-queries. If you need to create an or query, you can do so using the or method in the query object.

or takes a list of queries as argument, so a query might be built this way:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.or(q.q().f("counter").le(10),q.q().f("name").eq("Morphium"));

This would create an OR-Query asking for all "MyEntities", that have a counter less than or equal to 10 OR whose name is "Morphium". You can add as much or-queries as you like. OR-Queries can actually be combined with and queries as well:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.f("counter").ge(2);
    q=q.or(q.q().f("counter").le(10),q.q().f("name").eq("Morphium"));`

In that case, the query would be something like: counter is greater than 2 AND (counter is less then or equal to 10 OR name is "Morphium")

Combining and and or-queries is also possible, although the syntax would look a bit unfamiliar:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.f("counter").lt(100).or(q.q().f("counter").mod(3,0),q.q().f("value").ne("v");

This would create a query returning all entries that do have a counter of less than 100 AND where the modulo to base 3 of the value counter equals 0, and the value of the field value equals "v".

Quite complex, eh?

Well, there is more to it... it is possible, to create a query using a "where"-String... there you can add JavaScript code for your query. This code will be executed at the mongodb node, executing your query:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.where("this.counter > 10");

Attention: you can javascript code in that where clause, but you cannot access the db object there. This was changed when switching to Mongodb 2.6 with V8 Javascript engine

Declarative Caching

Using the @Cache annotation, you can define cache settings on a per type (= class) basis. This is done totally in background, handled by Morphium 100% transparently. You just add the annotation to your entities and you're good to go. See [Cache] and [Cache Synchronization]

Cache Synchronization

Cache synchronization was already mentioned above. The system of cache synchronization needs a messaging subsystem (see [Messaging] below). You just need to start the cache synchronizer yourself, if you want caches to be synchronized.

CacheSynchronizer cs=new CacheSynchronizer(morphium);
cs.start();

If you want to stop your cache synchronizing process, just call cs.setRunning(false); . The synchronizer will stop after a while (depending on your cache synchronization timeout).

By default no cache synchronizer is running.

Cluster Awareness

Morphium is cluster aware in a sense, that it does poll the state of a replicates periodically in order to know what nodes are life and need to be taken into account. (Same does the Java Driver, this information is now moved into the morphium driver implementation, so the double check is not necessary anymore).

Morphium also has support for clusters using it. Like a cluster of tomcats instances. In this case, Morphium is able to synchronize the caches of those cluster nodes.

Messaging

Morphium supports a simple Messaging system which uses mongoldb as storage. The messaging is more or less transactional (to the extend that mongo gives) and works multithreaded. To use messaging you only need to instantiate a Messaging-Instance. You may add listeners to this instance to process the messages and you may send messages through this instance.

Messaging is 100% multithreaded and thread safe.

Bulk Operations Support

All operations regarding lists (list updates, writing lists of objects, deleting lists of objects) will be implemented using the new bulk operation available since mongodb 2.6. This gives significant speed boost and adds reliability.

Actually, all method calls to mongo support a list of documents as argument. This means, you can send a list of updates, a list of documents to be inserted, a list of whatever. The ´BulkOperationContext´ only gathers those requests on the java side together, so that they can be sent in one call, instead of several.

With Morphium 3.0 an own implementation of this bulk operation context was introduced.

Callbacks

You can add a number of Listeners to Morphium in order to be informed about what happens, or to influence the way things are handled.

  • MorphiumStorageListeners: will be informed about any write process within morpheme. You can also veto if necessary. Works similar to [Lifecycle] methods, but for all entities.
  • CacheListener: Can be added to Morphium cache, will be informed about things to be added to cache, or if something would be updated or cleared. In all cases, a veto is possible.
  • ShutdownListener: if the system shuts down, you can be informed using this listener. It's not really Morphium specific.
  • ProfilingListener: will be informed about any read or write access to mongo and how long it took. This is useful if you want to track long requests or index misses.

In addition to that, almost all calls to mongo can be done asynchronously - either by defining that in the @Entity annotation or by defining it directly.

That means, an asList() call on a query object can take an AsyncCallback as argument, which then will be called, when the batch is ready. (which also means, the asList call will return null, the batch will be passed on in the callback).

Support for Aggregation

Morphium does have support for Aggregation in mongo. The aggregation Framework was introduced in mongo with V2.6 and is a alternative to MapReduce (which is still used). We implemented support for the new Aggregation framework into mongo. Up until now, there was no request for MapReduce - if you need it, please let me know.

Here is how the aggregation framework is used from mongo (see more info on the aggregation framework at MongoDb

This is the Unit test for Aggregation support in Mongo:

    @Test public void aggregatorTest() throws Exception { 
        createUncachedObjects(1000);
    
        Aggregator a = MorphiumSingleton.get().createAggregator(UncachedObject.class, Aggregate.class);
        assert (a.getResultType() != null);
        //eingangsdaten reduzieren
        a = a.project("counter");
        //Filtern
        a = a.match(MorphiumSingleton.get().createQueryFor(UncachedObject.class).f("counter").gt(100));
        //Sortieren - für $first/$last
        a = a.sort("counter");
        //limit der Daten
        a = a.limit(15);
        //group by - in dem Fall ALL, könnte auch beliebig sein
        a = a.group("all").avg("schnitt", "$counter").sum("summe", "$counter").sum("anz", 1).last("letzter", "$counter").first("erster", "$counter").end();
        //ergebnis projezieren 
        HashMap projection=new HashMap<>()
        projection.put("summe",1);
        projection.put("anzahl","$anz");
        projection.put("schnitt",1);
        projection.put("last","$letzter");
        projection.put("first","$erster");
        a = a.project(projection);
    
        List obj = a.toAggregationList();
        for (DBObject o : obj) {
            log.info("Object: " + o.toString());
        }
        List lst = a.aggregate();
        assert (lst.size() == 1) : "Size wrong: " + lst.size();
        log.info("Sum  : " + lst.get(0).getSumme());
        log.info("Avg  : " + lst.get(0).getSchnitt());
        log.info("Last :    " + lst.get(0).getLast());
        log.info("First:   " + lst.get(0).getFirst());
        log.info("count:  " + lst.get(0).getAnzahl());
    
    
        assert (lst.get(0).getAnzahl() == 15) : "did not find 15, instead found: " + lst.get(0).getAnzahl();
    
    }
    
    
     @Embedded 
     public static class Aggregate { 
        private double schnitt; 
        private long summe; 
        private int last; 
        private int first; 
        private int anzahl;
    
        @Property(fieldName = "_id")
        private String theGeneratedId;
    
        public int getAnzahl() {
            return anzahl;
        }
    
        public void setAnzahl(int anzahl) {
            this.anzahl = anzahl;
        }
    
        public int getLast() {
            return last;
        }
    
        public void setLast(int last) {
            this.last = last;
        }
    
        public int getFirst() {
            return first;
        }
    
        public void setFirst(int first) {
            this.first = first;
        }
    
        public double getSchnitt() {
            return schnitt;
        }
    
        public void setSchnitt(double schnitt) {
            this.schnitt = schnitt;
        }
    
        public long getSumme() {
            return summe;
        }
    
        public void setSumme(long summe) {
            this.summe = summe;
        }
    
        public String getTheGeneratedId() {
            return theGeneratedId;
        }
    
        public void setTheGeneratedId(String theGeneratedId) {
            this.theGeneratedId = theGeneratedId;
        }
    }

The class Aggregate is used to hold the batch of the aggregation.

Validation

If javax.validation can be found in class path, you are able to validate values of your entities using the validation annotations. Those validations will take place before the object would be saved.

Technically it's implemented as a JavaxValidationStorageListener which is a storage listener and vetoes the write operation if validation fails.

an example on how to use validation:

    @Id private MorphiumId id;
    
    @Min(3)
    @Max(7)
    private int theInt;
    
    @NotNull
    private Integer anotherInt;
    
    @Future
    private Date whenever;
    
    @Pattern(regexp = "m[ueü]nchen")
    private String whereever;
    
    @Size(min = 2, max = 5)
    private List friends;
    
    @Email
    private String email;
    

Those validation rules will be enforced upon storing the corresponding object:

    @Test(expected = ConstraintViolationException.class)
    public void testNotNull() {
        ValidationTestObject o = getValidObject();
        o.setAnotherInt(null);
        MorphiumSingleton.get().store(o);
    }

Polymorphism

Its possible to have different type of entities stored in one collection. Usually this will only make sense if those entities have some things in common. In an object oriented way: they are derived from one single entity.

In order to make this work, you have to tell Morphium that you want to use a certain entity in a polymorph way (property of the annotation @Entity). If so, the full qualified class name will be stored in the mongo document representing the entity. Actually, you can store any type of entity into one list, if each of those types is marked polymorph. Only reading them is a bit hard, as you would iterate over Objects and would have to decide on type yourself.

Async API

Fully Customizable

Description

on the following lines you get a more in depth view of the

Names of entities and fields

Morphium by defaults converts all java CamelCase identifiers in underscore separated strings. So, MyEntity will be stored in an collection called my_entity and the field aStringValue would be stored in as a_string_value.

When specifying a field, you can always use either the transformed name or the name of the corresponding java field. Collection names are always determined by the classname itself.

CamelCase conversion

But in Morphium you can of course change that behaviour. Easiest way is to switch off the transformation of CamelCase globally by setting camelCaseConversionEnabled to false (see above: Configuration). If you switch it off, its off completely - no way to do switch it on for just one collection or so.

If you need to have only several types converted, but not all, you have to have the conversion globally enabled, and only switch it off for certain types. This is done in either the @Entity or @Embedded annotation.

@Entity(convertCamelCase=false)
public class MyEntity {
   private String myField;`

This example will create a collection called MyEntity (no conversion) and the field will be called myField in mongo as well (no conversion).

Attention: Please keep in mind that, if you switch off camelCase conversion globally, nothing will be converted!

using the full qualified classname

you can tell Morphium to use the full qualified classname as basis for the collection name, not the simple class name. This would batch in createing a collection de_caluga_morphium_my_entity for a class called de.caluga.morphium.MyEntity. Just set the flag useFQN in the entity annotation to true.

@Entity(useFQN=true)
public class MyEntity {`

Recommendation is, not to use the full qualified classname unless it's really needed.

Specifying a collection / fieldname

In addition to that, you can define custom names of fields and collections using the corresponding annotation (@Entity, @Property).

For entities you may set a custom name by using the collectionName value for the annotation:

@Entity(collectionName="totallyDifferent") 
public class MyEntity {
    private String myValue;

the collection name will be totallyDifferent in mongo. Keep in mind that camel case conversion for fields will still take place. So in that case, the field name would probably be my_value. (if camel case conversion is enabled in config)

You can also specify the name of a field using the property annotation:

@Property(fieldName="my_wonderful_field")
private String something;`

Again, this only affects this field (in this case, it will be called my_wondwerful_field in mongo) and this field won't be converted camelcase. This might cause a mix up of cases in your mongodb, so please use this with care.

Accessing fields

When accessing fields in Morphium (especially for the query) you may use either the name of the Field in Java (like myEntity) or the converted name depending on the config (camelCased or not, or custom).

Using NameProviders

In some cases it might be necessary to have the collection name calculated dynamically. This can be acchieved using the NameProvider Interface.

You can define a NameProvider for your entity in the @Entity annotation. You need to specify the type there. By default, the NameProvider for all Entities is DefaultNameProvider. Which acutally looks like this:

    public final class DefaultNameProvider implements NameProvider {
    
    @Override
    public String getCollectionName(Class type, ObjectMapper om, boolean translateCamelCase, boolean useFQN, String specifiedName, Morphium morphium) {
 
        String name = type.getSimpleName();
    
        if (useFQN) {
            name = type.getName().replaceAll("\\.", "_");
        }
        if (specifiedName != null) {
            name = specifiedName;
        } else {
            if (translateCamelCase) {
                name = morphium.getARHelper().convertCamelCase(name);
            }
        }
        return name;
    }
    
    
    }
    

You can use your own provider to calculate collection names depending on time and date or for example depending on the querying host name (like: create a log collection for each server separately or create a collection storing logs for only one month each).

Attention: Name Provider instances will be cached, so please implement them threadsafe.

Entity Definition

Entitys in Morphium ar just "Plain old Java Objects" (POJOs). So you just create your data objects, as usual. You only need to add the annotation @Entity to the class, to tell Morphium "Yes, this can be stored". The only additional thing you need to take care of is the definition of an ID-Field. This can be any field in the POJO identifying the instance. Its best, to use MorphiumId as type of this field, as these can be created automatically and you don't need to care about those as well.

If you specify your ID to be of a different kind (like String), you need to make sure, that the String is set, when the object will be written. Otherwise you might not find the object again. So the shortest Entity would look like this:

@Entity
public class MyEntity {
   @Id private MorphiumId id;
   //.. add getter and setter here
}

indexes

Indexes are very important in mongo, so you should definitely define your indexes as soon as possible during your development. Indexes can be defined on the Entity itself, there are several ways to do so: - @Id always creates an index - you can add an @Index to any field to have that indexed:

@Index
private String name;
  • you can define combined indexes using the @Index annotation at the class itself:

    @Index({"counter, name","value,thing,-counter"} public class MyEntity {

This would create two combined indexes: one with counter and name (both ascending) and one with value, thing and descending counter. You could also define single field indexes using this annotations, but it`s easier to read adding the annotation direktly to the field.

  • Indexes will be created automatically if you create the collection. If you want the indexes to be created, even if there is already data stores, you need to callmorphium.ensureIndicesFor(MyEntity.class)- You also may create your own indexes, which are not defined in annotations by callingmorphium.ensureIndex(). As parameter you pass on a Map containing field name and order (-1 or 1) or just a prefixed list of strings (like"-counter","name").

Every Index might have a set of options which define the kind of this index. Like buildInBackground or unique. You need to add those as second parameter to the Index-Annotation:

@Entity
 @Index(value = {"-name, timer", "-name, -timer", "lst:2d", "name:text"}, 
            options = {"unique:1", "", "", ""})
public static class IndexedObject {

here 4 indexes are created. The first two ar more or less standard, wheres the lst index is a geospacial one and the index on name is a text index (only since mongo 2.6). If you need to define options for one of your indexes, you need to define it for all of them (here, only the first index is unique).

We're working on porting Morphium to java8, and there it will be possible to have more than one @Index annotation, making the syntax a bit more ledgeable

capped collections

Similar as with indexes, you can define you collection to be capped using the @Capped annotation. This annotation takes two arguments: the maximum number of entries and the maximum size. If the collection does not exist, it will be created as capped collection using those two values. You can always ensureCapped your collection, unfortunately then only the size parameter will be honored.

Querying

Querying is done via the Query-Object, which is created by Morphium itself (using the Query Factory). The definition of the query is done using the fluent interface:

    Query query=morphium.createQueryFor(MyEntity.class);
    query=query.f("id").eq(new MorphiumId());
    query=query.f("valueField").eq("the value");
    query=query.f("counter").lt(22);
    query=query.f("personName").matches("[a-zA-Z]+");
    query=query.limit(100).sort("counter");

In this example, I refer to several fields of different types. The Query itself is always of the same basic syntax:

    queryObject=queryObject.f(FIELDNAME).OPERATION(Value);
    queryObject=queryObject.skip(NUMBER)//skip a number of entreis
    queryObject=queryObject.limig(NUMBER)// limit batch
    queryObject.sort(FIELD_TO_SORTBY);  

As field name you may either use the name of the field as it is in mongo or the name of the field in java. If you specify an unknown field to Morphium, a RuntimeException will be raised.

For definition of the query, it's also a good practice to define enums for all of your fields. This makes it hard to have mistypes in a query:

    public class MyEntity {
      private MorphiumId id;
      private Double value;
      private String personName;
      private int counter;
      //.... field accessors
      public enum Fields { id, value, personName,counter, }
    }

There is a plugin for intelliJ creating those enums automatically. Then, when defining the query, you don't have to type in the name of the field, just use the field enum:

query=query.f(MyEntity.Fields.counter).eq(123);

After you defined your query, you probably want to access the data in mongo. Via Morphium,there are several possibilities to do that: - queryObject.get(): returns the first object matching the query, only one. Or null if nothing matched - queryObject.asList(): return a list of all matching objects. Reads all data in RAM. Useful for small amounts of data - Iterator<MyEntity> it=queryObject.asIterator(): creates a MorphiumIterator to iterate through the data, whch does not read all data at once, but only a couple of elements in a row (default 10).

the Iterators

Morphium has support for special Iterators, which steps through the data, a couple of elements at a time. By Default this is the standard behaviour. But the _Morphium_Iterator ist quite capable:

  • queryObject.asIterable() will step through the results batch by batch. The batch size is determined by the driver settings. This is the most performant, but lacks the ability to "step back" out of the current processed batch.
  • queryObject.asIterable(100) will step through the batch list, 100 at a time using a mongodb cursor iterator.
  • queryObject.asIterable(100,5) will step through the batch list, 100 at a time and keep 5 chunks of 100 elements each as prefetch buffers. Those will be filled in background.
  • queryObject.asIterable(100,1) actually the same as .asIterable(100) but using a query based iterator instead.
  • queryObject.asIterable(100, new PrefetchingIterator())): this is more or less the same as the prefetching above, but using the query based PrefetchingIterator. This is fetching the datachunks using skip and limit functionality of mongodb which showed some decrease in performance, the higher the skip is. It's still there for compatibility reasons.

Internally the default iterator does create queries that are derived from the sort of the query, if there is no sort specified, it will assume you want to sort by _id.

you could put each of those iterators to one of two classes:

  1. the iterator is using the Mongodb Cursor
  2. the iterator is using distinct queries for each step / chunk.

these have significant different behaviour.

query based iterators

the query based iterators use the usual query method of morphium. hence all related functionalities work, like caching, life cycle methods etc. It is just like you would create those queries in a row. one by one.

cursor based iterators

due to the fact that the query is being executed portion by portion, there is no way of having things cached properly. These queries do not use the cache!

Storing

Storing is more or less a very simple thing, just call morphium.store(pojo) and you're done. Although there is a bit more to it: - if the object does not have an id (id field is null), there will be a new entry into the corresponding collection. - if the object does have an id set (!= null), an update to db is being issued. - you can call morphium.storeList(lst) where lst is a list of entities. These would be stored in bulkd, if possible. Or it does a bulk update of things in mongo. Even mixed lists (update and inserts) are possible. Morphium will take care of sorting it out - there are additional methods for writing to mongo, like update operations set, unset, push, pull and so on (update a value on one entity or for all elements matching a query), delete objects or objects matching a query, and a like - The writer that acutally writes the data, is chosen depending on the configuration of this entity (see Annotations below)

Annotations

a lot of things can be configured in Morphium using annotations. Those annotations might be added to either classes, fields or both.

Entity

Perhaps the most important Annotation, as it has to be put on every class the instances of which you want to have stored to database. (Your data objects).

By default, the name of the collection for data of this entity is derived by the name of the class itself and then the camel case is converted to underscore strings (unless config is set otherwise).

These are the settings available for entities:

  • translateCamelCase: default true. If set, translate the name of the collection and all fields (only those, which do not have a custom name set)
  • collectionName: set the collection name. May be any value, camel case won't be converted.
  • useFQN: if set to true, the collection name will be built based on the full qualified class name. The Classname itself, if set to false. Default is false
  • polymorph: if set to true, all entities of this type stored to mongo will contain the full qualified name of the class. This is necessary, if you have several different entities stored in the same collection. Usually only used for polymorph lists. But you could store any polymorph marked object into that collection Default is false
  • nameProvider: specify the class of the name provider, you want to use for this entity. The name provider is being used to determine the name of the collection for this type. By Default it uses the DefaultNameProvider (which just uses the classname to build the collection name). see above

Embedded

Marks POJOs for object mapping, but don't need to have an ID set. These objects will be marshaled and unmarshaled, but only as part of another object (Subdocument). This has to be set at class level.

You can switch off camel case conversion for this type and determine, whether data might be used polymorph.

Capped

Valid at: Class level

Tells Morphium to create a capped collection for this object (see capped collections above).

Parameters:

maxSizemaximum size in byte. Is used when converting to a capped collection
maxNumbernumber of entries for this capped collection

AdditionalData

Special feature for Morphium: this annotation has to be added for at lease one field of type Map<String,Object>. It does make sure, that all data in Mongo, that cannot be mapped to a field of this entity, will be added to the annotated Map properties.

by default this map is read only. But if you want to change those values or add new ones to it, you can set readOnly=false

Aliases

It's possible to define aliases for field names with this annotation (hence it has to be added to a field).

 @Alias({"stringList","string_list"})
List<String> strLst;

in this case, when reading an object from Mongodb, the name of the field strLst might also be stringList or string_list in mongo. When storing it, it will always be stored as strLst or str_lst according to config.

This feature comes in handy when migrating data.

CreationTime

has to be added to both the class and the field(s) to store the creation time in. This value is set in the moment, the object is being stored to mongo. The data type for creation time might be:

  • long / Long: store as timestamp
  • Eate: store as date object
  • String: store as a string, you may need to specify the format for that

LastAccess

same as creation time, but storing the last access to this type. Attention: will cause all objects read to be updated and written again with a changed timestamp.

Usage: find out, which entries on a translation table are not used for quite some time. Either the translation is not necessary anymore or the corresponding page is not being used.

LastChange

Same as the two above, except the timestamp of the last change (to mongo) is being stored. The value will be set, just before the object is written to mongo.

DefaultReadPreference

Define the read preference level for an entity. This annotation has to be used at class level. Valid types are:

  • PRIMARY: only read from primary node
  • PRIMARY_PREFERED: if possible, use primary.
  • SECONDARY: only read from secondary node
  • SECONDARY_PREFERED: if possible, use secondary
  • NEAREST: I don't care, take the fastest

Id

Very important annotation to a field of every entity. It marks that field to be the id and identify any object. It will be stored as _id in mongo (and will get an index).

The Id may be of any type, though usage of ObjectId (or MorphiumId in Java) is strongly recommended.

Index

Define indexes. Indexes can be defined for a single field. Combined indexes need to be defined on class level. See above.

PartialUpdate

If this annotation is present for an entity, this entity would only send changes to mongo when being stored. This is useful for big objects, which only contain small changes.

Attention: in the background your object is being replaced by a Proxy-Object to collect the changes.

Property

Can be added to any field. This not only has documenting character, it also gives the opportunity to change the name of this field by setting the fieldName value. By Default the fieldName is ".", which means "fieldName based".

ReadOnly

Mark an entity to be read only. You'll get an exception when trying to store.

Reference

If you have a member variable, that is a POJO and not a simple value, you can store it as reference to a different collection, if the POJO is an Entity (and only if!).

This also works for lists and Maps. Attention: when reading Objects from disk, references will be de-referenced, which will batch into one call to mongo each.

Unless you set lazyLoading to true, in that case, the child documents will only be loaded when accessed.

transient

Do not store the field.

UseIfnull

Usually, Morphium does not store null values at all. That means, the corresponding document just would not contain the given field(s) at all.

Sometimes that might cause problems, so if you add @UseIfNull to any field, it will be stored into mongo even if it is null.

WriteOnly

Sometimes it might be useful to have an entity set to write only (logs). An exception will be raised, if you try to query such a entity.

WriteSafety

Sepcify the safety for this entity when it comes to writing to mongo. This can range from "NONE" to "WAIT FOR ALL SLAVES". Here are the available settings:

  • timeout: set a timeout in ms for the operation - if set to 0, unlimited (default). If set to negative value, wait relative to replication lag
  • level: set the safety level:
    • IGNORE_ERRORS None, no checking is done
    • NORMAL None, network socket errors raised
    • BASIC Checks server for errors as well as network socket errors raised
    • WAIT_FOR_SLAVE Checks servers (at lease 2) for errors as well as network socket errors raised
    • MAJORITY Wait for at least 50% of the slaves to have written the data
    • WAIT_FOR_ALL_SLAVES: waits for all slaves to have committed the data. This is depending on how many slaves are available in replica set. Wise timeout settings are important here. See WriteConcern in MongoDB Java-Driver for additional information

AsyncWrites

If this annotation is present at a given entity, all write access concerning this type would be done asynchronously. That means, the write process will start immediately, but run in background.

You won't be informed about errors or success. If you want to do that, you don't need to set @AsyncWrites, use one of the save method with a Callback for storing your data - those methods are all asynchronous.

WriteBuffer

Create a write buffer, do not write data directly to mongo, but wait for the buffer to be filled a certain amount:

  • size: default 0, max size of write Buffer entries, 0 means unlimited. STRATEGY is meaningless then
  • strategy: define what happens when write buffer is full and new things would be written. Can be one of WRITE_NEW, WRITE_OLD, IGNORE_NEW, DEL_OLD, JUST_WARN
    • WRITE_NEW: write all new incoming entries to the buffer directly to mongo, buffer won't grow
    • WRITE_OLD: take one of the oldest entries from the buffer, write it, queue the new entry to buffer. Buffer won't grow
    • IGNORE_NEW: do not add new entry to buffer and do not write it. Attention: possible data loss Buffer won't grow
    • DEL_OLD: delete an old entry from the buffer, add new one. Buffer won't grow
    • JUST_WARN: just issue a warning via log4j, but add the new Object anyway. Buffer will grow, no matter what threshold is set!

Cache

Read-Cache Settings for the given entity.

  • timeout: How long are entries in cache valid, in ms. Default 60000ms
  • clearOnWrite: if set to true (default) the cache will be cleared, when you store or update an instance of this type
  • maxEntries: Maximum number of entries in cache for this type. -1 means infinite
  • clearStrategy: when reaching the maximum number of entries, how to replace entries in cache.
    • LRU: remove the least recently used entry from cache, add the new
    • RANDOM: remove a random entry from cache, add the new
    • FIFO: remove the oldest entry from cache, add the new (default)
  • syncCache: Set the strategy for syncing cache entries of this type. This is useful when running in a clustered environment to inform all nodes of the cluster to change their caches accordingly. A sync message will be sent to all nodes using the Morphium messaging as soon as an item of this type is written to mongo.
    • NONE: No cache sync
    • CLEAR_TYPE_CACHE: clear the whole cache for this type on all nodes
    • REMOVE_ENTRY_FROM_TYPE_CACHE: remove an updated entry from the type cache of all nodes
    • UPDATE_ENTRY: update the entry in the cache on all nodes
    • This may cause heavy load on the messaging system. All sync strategies except CLEAR_TYPE_CACHE might batch in dirty reads on some nodes.

NoCache

Explicitly disable cache for this type. This is important if you have a hierarchy of entities and you want the "super entity" to be cached, but inherited entities from that type not.

Lifecycle

This is a marker annotation telling Morphium that in this type, there are some Lifecycle callbacks to be called.

Please keep in mind that all lifecycle annotations (see below) would be ignored, if this annotation is not added to the type.

PostLoad

If @Lifecycle is added to the type, @PostLoad may define the method to be called, after the object was read from mongo.

PreStore

If @Lifecycle is added to the type, @PreStore may define the method to be called, just before the object is written to mongo. It is possible to throw an Exception here to avoid storage of this object.

PostStore

If @Lifecycle is added to the type, @PostStore may define the method to be called, after the object was written to mongo.

PreRemove

If @Lifecycle is added to the type, @PreRemove may define the method to be called, just before the object would be removed from mongo. You might throw an exception here to avoid storage.

PostRemove

If @Lifecycle is added to the type, @PostRemove may define the method to be called, after the object was removed from mongo.

PreUpdate

If @Lifecycle is added to the type, @PreUpdate may define the method to be called, just before the object would be updated in mongo. Veto is possible by throwing an Exception.

PostUpdate

If @Lifecycle is added to the type, @PostUpdate may define the method to be called, after the object was updated in mongo.

Dependencies

Morphium does not have many dependencies:

  • log4j
  • mongo java driver (usually the latest version available at that time)
  • a simple json parser (json-simple)

Here is the excerpt from the pom.xml:

<dependency> 
  <groupid>cglib</groupid> 
  <artifactid>cglib</artifactid> 
  <version>2.2.2</version> 
</dependency> 
<dependency> 
  <groupid>log4j</groupid> 
  <artifactid>log4j</artifactid> 
  <version>1.2.17</version> 
</dependency>
<dependency> 
  <groupid>org.mongodb</groupid> 
  <artifactid>mongo-java-driver</artifactid> 
  <version>2.12.3</version> 
</dependency>
<dependency> 
  <groupid>com.googlecode.json-simple</groupid> 
  <artifactid>json-simple</artifactid> 
  <version>1.1</version> 
</dependency>

There is one kind of "optional" Dependency: If hibernate validation is available, it's being used. If it cannot be found in class path, it's no problem.

Code Examples

All those Code examples are part of the Morphium source distribution. All of the codes are at least part of a unit test.

Simple Write / Read

for (int i = 1; i <= NO_OBJECTS; i++) { 
    UncachedObject o = new UncachedObject(); 
    o.setCounter(i); 
    o.setValue("Uncached " + i % 2); 
    MorphiumSingleton.get().store(o); 
 } 
 Query<uncachedobject> q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
 q = q.f("counter").gt(0).sort("-counter", "value");
 List</uncachedobject><uncachedobject> lst = q.asList();
 assert (!lst.get(0).getValue().equals(lst.get(1).getValue()));

    q = q.q().f("counter").gt(0).sort("value", "-counter");
    List<UncachedObject> lst2 = q.asList();
    assert (lst2.get(0).getValue().equals(lst2.get(1).getValue()));
    log.info("Sorted");

    q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    q = q.f("counter").gt(0).limit(5).sort("-counter");
    int st = q.asList().size();
    q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    q = q.f("counter").gt(0).sort("-counter").limit(5);
    assert (st == q.asList().size()) : "List length differ?";

And:

Query<complexobject> q = MorphiumSingleton.get().createQueryFor(ComplexObject.class);

    q = q.f("embed.testValueLong").eq(null).f("entityEmbeded.binaryData").eq(null);
    String queryString = q.toQueryObject().toString();
    log.info(queryString);
    assert (queryString.contains("embed.test_value_long") && queryString.contains("entityEmbeded.binary_data"));
    q = q.f("embed.test_value_long").eq(null).f("entity_embeded.binary_data").eq(null);
    queryString = q.toQueryObject().toString();
    log.info(queryString);
    assert (queryString.contains("embed.test_value_long") && queryString.contains("entityEmbeded.binary_data"));

Asynchronous Write

@Test
public void asyncStoreTest() throws Exception {
    asyncCall = false;
    super.createCachedObjects(1000);
    waitForWrites();
    log.info("Uncached object preparation");
    super.createUncachedObjects(1000);
    waitForWrites();
    Query<UncachedObject> uc = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    uc = uc.f("counter").lt(100);
    MorphiumSingleton.get().delete(uc, new AsyncOperationCallback<Query<UncachedObject>>() {
        @Override
        public void onOperationSucceeded(AsyncOperationType type, Query<Query<UncachedObject>> q, long duration, List<Query<UncachedObject>> batch, Query<UncachedObject> entity, Object... param) {
            log.info("Objects deleted");
        }

        @Override
        public void onOperationError(AsyncOperationType type, Query<Query<UncachedObject>> q, long duration, String error, Throwable t, Query<UncachedObject> entity, Object... param) {
            assert false;
        }
    });

    uc = uc.q();
    uc.f("counter").mod(3, 2);
    MorphiumSingleton.get().set(uc, "counter", 0, false, true, new AsyncOperationCallback<UncachedObject>() {
        @Override
        public void onOperationSucceeded(AsyncOperationType type, Query<UncachedObject> q, long duration, List<UncachedObject> batch, UncachedObject entity, Object... param) {
            log.info("Objects updated");
            asyncCall = true;

        }

        @Override
        public void onOperationError(AsyncOperationType type, Query<UncachedObject> q, long duration, String error, Throwable t, UncachedObject entity, Object... param) {
            log.info("Objects update error");
        }
    });

    waitForWrites();

    assert MorphiumSingleton.get().createQueryFor(UncachedObject.class).f("counter").eq(0).countAll() > 0;
    assert (asyncCall);
}

Asynchronous Read

@Test
public void asyncReadTest() throws Exception {
    asyncCall = false;
    createUncachedObjects(100);
    Query<UncachedObject> q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    q = q.f("counter").lt(1000);
    q.asList(new AsyncOperationCallback<UncachedObject>() {
        @Override
        public void onOperationSucceeded(AsyncOperationType type, Query<UncachedObject> q, long duration, List<UncachedObject> batch, UncachedObject entity, Object... param) {
            log.info("got read answer");
            assert (batch != null) : "Error";
            assert (batch.size() == 100) : "Error";
            asyncCall = true;
        }

        @Override
        public void onOperationError(AsyncOperationType type, Query<UncachedObject> q, long duration, String error, Throwable t, UncachedObject entity, Object... param) {
            assert false;
        }
    });
    waitForAsyncOperationToStart(1000000);
    int count = 0;
    while (q.getNumberOfPendingRequests() > 0) {
        count++;
        assert (count < 10);
        System.out.println("Still waiting...");
        Thread.sleep(1000);
    }
    assert (asyncCall);
}

Iterator

@Test
public void basicIteratorTest() throws Exception {
    createUncachedObjects(1000);

    Query<UncachedObject> qu = getUncachedObjectQuery();
    long start = System.currentTimeMillis();
    MorphiumIterator<UncachedObject> it = qu.asIterable(2);
    assert (it.hasNext());
    UncachedObject u = it.next();
    assert (u.getCounter() == 1);
    log.info("Got one: " + u.getCounter() + "  / " + u.getValue());
    log.info("Current Buffersize: " + it.getCurrentBufferSize());
    assert (it.getCurrentBufferSize() == 2);

    u = it.next();
    assert (u.getCounter() == 2);
    u = it.next();
    assert (u.getCounter() == 3);
    assert (it.getCount() == 1000);
    assert (it.getCursor() == 3);

    u = it.next();
    assert (u.getCounter() == 4);
    u = it.next();
    assert (u.getCounter() == 5);

    while (it.hasNext()) {
        u = it.next();
        log.info("Object: " + u.getCounter());
    }

    assert (u.getCounter() == 1000);
    log.info("Took " + (System.currentTimeMillis() - start) + " ms");
}

Messaging

@Test
public void messagingTest() throws Exception {
    error = false;

    MorphiumSingleton.get().clearCollection(Msg.class);

    final Messaging messaging = new Messaging(MorphiumSingleton.get(), 500, true);
    messaging.start();

    messaging.addMessageListener(new MessageListener() {
        @Override
        public Msg onMessage(Messaging msg, Msg m) {
            log.info("Got Message: " + m.toString());
            gotMessage = true;
            return null;
        }
    });
    messaging.storeMessage(new Msg("Testmessage", MsgType.MULTI, "A message", "the value - for now", 5000));

    Thread.sleep(1000);
    assert (!gotMessage) : "Message recieved from self?!?!?!";
    log.info("Dig not get own message - cool!");

    Msg m = new Msg("meine Message", MsgType.SINGLE, "The Message", "value is a string", 5000);
    m.setMsgId(new MorphiumId());
    m.setSender("Another sender");

    MorphiumSingleton.get().store(m);

    Thread.sleep(5000);
    assert (gotMessage) : "Message did not come?!?!?";

    gotMessage = false;
    Thread.sleep(5000);
    assert (!gotMessage) : "Got message again?!?!?!";

    messaging.setRunning(false);
    Thread.sleep(1000);
    assert (!messaging.isAlive()) : "Messaging still running?!?";
}

Cache Synchronization

@Test
public void cacheSyncTest() throws Exception {
    MorphiumSingleton.get().dropCollection(Msg.class);
    createCachedObjects(1000);

    Morphium m1 = MorphiumSingleton.get();
    MorphiumConfig cfg2 = new MorphiumConfig();
    cfg2.setAdr(m1.getConfig().getAdr());
    cfg2.setDatabase(m1.getConfig().getDatabase());

    Morphium m2 = new Morphium(cfg2);
    Messaging msg1 = new Messaging(m1, 200, true);
    Messaging msg2 = new Messaging(m2, 200, true);

    msg1.start();
    msg2.start();

    CacheSynchronizer cs1 = new CacheSynchronizer(msg1, m1);
    CacheSynchronizer cs2 = new CacheSynchronizer(msg2, m2);
    waitForWrites();

    //fill caches
    for (int i = 0; i < 1000; i++) {
        m1.createQueryFor(CachedObject.class).f("counter").lte(i + 10).asList(); //fill cache
        m2.createQueryFor(CachedObject.class).f("counter").lte(i + 10).asList(); //fill cache
    }
    //1 always sends to 2....


    CachedObject o = m1.createQueryFor(CachedObject.class).f("counter").eq(155).get();
    cs2.addSyncListener(CachedObject.class, new CacheSyncListener() {
        @Override
        public void preClear(Class cls, Msg m) throws CacheSyncVetoException {
            log.info("Should clear cache");
            preClear = true;
        }

        @Override
        public void postClear(Class cls, Msg m) {
            log.info("did clear cache");
            postclear = true;
        }

        @Override
        public void preSendClearMsg(Class cls, Msg m) throws CacheSyncVetoException {
            log.info("will send clear message");
            preSendClear = true;
        }

        @Override
        public void postSendClearMsg(Class cls, Msg m) {
            log.info("just sent clear message");
            postSendClear = true;
        }
    });
    msg2.addMessageListener(new MessageListener() {
        @Override
        public Msg onMessage(Messaging msg, Msg m) {
            log.info("Got message " + m.getName());
            return null;
        }
    });
    preSendClear = false;
    preClear = false;
    postclear = false;
    postSendClear = false;
    o.setValue("changed it");
    m1.store(o);

    Thread.sleep(1000);
    assert (!preSendClear);
    assert (!postSendClear);
    assert (postclear);
    assert (preClear);
    Thread.sleep(60000);

    long l = m1.createQueryFor(Msg.class).countAll();
    assert (l <= 1) : "too many messages? " + l;
//        createCachedObjects(50);
//        Thread.sleep(90000); //wait for messages to be cleared
//        assert(m1.createQueryFor(Msg.class).countAll()==0);
    cs1.detach();
    cs2.detach();
    msg1.setRunning(false);
    msg2.setRunning(false);
    m2.close();
}
@Test
public void nearTest() throws Exception {
    MorphiumSingleton.get().dropCollection(Place.class);
    ArrayList<Place> toStore = new ArrayList<Place>();
//        MorphiumSingleton.get().ensureIndicesFor(Place.class);
    for (int i = 0; i < 1000; i++) {
        Place p = new Place();
        List<Double> pos = new ArrayList<Double>();
        pos.add((Math.random() * 180) - 90);
        pos.add((Math.random() * 180) - 90);
        p.setName("P" + i);
        p.setPosition(pos);
        toStore.add(p);
    }
    MorphiumSingleton.get().storeList(toStore);

    Query<Place> q = MorphiumSingleton.get().createQueryFor(Place.class).f("position").near(0, 0, 10);
    long cnt = q.countAll();
    log.info("Found " + cnt + " places around 0,0 (10)");
    List<Place> lst = q.asList();
    for (Place p : lst) {
        log.info("Position: " + p.getPosition().get(0) + " / " + p.getPosition().get(1));
    }
}

@Index("position:2d")
@NoCache
@WriteBuffer(false)
@WriteSafety(level = SafetyLevel.MAJORITY)
@DefaultReadPreference(ReadPreferenceLevel.PRIMARY)
@Entity
public static class Place {
    @Id
    private MorphiumId id;

    public List<Double> position;
    public String name;

    public MorphiumId getId() {
        return id;
    }

    public void setId(MorphiumId id) {
        this.id = id;
    }

    public List<Double> getPosition() {
        return position;
    }

    public void setPosition(List<Double> position) {
        this.position = position;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

the problems with Logging

today there is a whole bunch of loggin frameworks. Every one is more capable than the other. Most commond probably are java.util.logging and log4j. Morphium used log4j quite some time. But in our high load environment we encountered problems with the logging itself. Also we had problems, that every library did use a different logging framework.

Morphium since V2.2.21 does use its own logger. This can be configured using Environment variables (in linux like export morphium_log_file=/var/log/morphium.log) or java system parameters (like java -Dmorphium.log.level=5).

This logger is built for performance and thread safety. It works find in high load environments. And has the following features:

  • it is instanciated with new - no singleton. Lesser performance / synchronization issues
  • it has several options for configuration. (see above). You can define global settings like morphium.log.file but you can also define settings for a prefix of a fqdn, like morphium.log.file.de.caluga.morphium. For example java -Dmorphium.log.level=2 -Dmorphium.log.level.de.caluga.morphium.messaging=5 would switch on debugging only for the messaging package, the default has level 2 (which is ERROR)
  • it is possible to define 3 Things in the way described above (either global or class / package sepcific): FileName (real path, or STDOUT or STDERR), Log level (0=none, 1=FATAL, 2=ERROR, 3=WARN, 4=INFO, 5=DEBUG) and whether the output should be synced or buffered (synced=false)
  • if you want to use log4j or java.util.logging as logging, you can set the log filename to log4j or jul accordingly
  • if you want to use your own logging implementation, just tell morphium the log delegate as filename, e.g. morphium.log.file=de.caluga.morphium.log.MyLogDelegate
  • of course, all this configuration can be done in code as well.

Swtiching to logback in V3.2.0

Yes, keeping an own addintional logger framework alive is not the smartest or easiest thing to do. So we decided to use logback for configuration of logging, using slf4j in morphium ourselves (in performance checks this seemed to have almost no negative impact fortunately)

So with upcoming V3.2.0 the own logger implementation is gone...


Kategorie: Sicherheit

Security und Passwortstrategie

Do, 06. 03. 2014 - Tags: security

Security und Passwortstrategie

Es ist heutzutage ja nicht so einfach, sich alle Pins, Passwörter und Logins zu merken. Für jedes Forum und jeden Dienst im Internet muss man sich authentifizieren - schon aus rechtlichen gründen. Aber gerade im Zuge der letzten Enthüllungen über die Aktivitäten der Geheimdienste, sollte jeder sich mal überlegen, was er für Passwörter nutzt.

Passwortsicherheit - lang == sicher?

Kurz gesagt: von wegen

Die etwas längere Antwort ist, dass es heutzutage viel einfacher ist, das Passwort clever zu erraten, als kompliziert irgendwelche Buffer Overflows oder sonstige Exploits zu finden und auszunutzen. Hacker brauchen meist gar nicht lange nach dem Passwort suchen, google nennt einem häufig ein paar Kandidaten: Vorname der Ehefrau, Name des Haustiers, Geburtsdatum der Kinder etc. Mit diesem "Social Engineering" kann man die Zahl möglicher Passwörter stark reduzieren, da diese ja nicht wirklich zufällig erstellt werden. Im Allgemeinen sind Passwörter, die auf Worten basieren, mit einer Silben basierten brute Force Attacke recht schnell zu knacken, oft in Minuten. Ein gleich langes zufällig gewähltes Passwort ist dann aber vielleicht erst nach Stunden oder Tagen gefunden. Beispiel: nehmen wir das Passwort 'supermann'. Das besteht aus 9 Zeichen. Wenn ein Passwort mit 9 Zeichen wirklich zufällig wäre, müsste ein Hacker theoretisch 26 hoch 9 Versuche (= 5.429.503.678.976 ) machen (wenn man davon ausgeht, dass nur normale Buchstaben, keine Umlaute verwendet wurden), im das Passwort sicher rauszufinden. Allerdings besteht das Passwort nur aus drei Silben. Silben gibt es im deutschen je nachdem, wen man fragt, verschieden viele, aber wohl zumindest weniger als 1000. d.h. ich muss nicht mehr 26 hoch 9 Kandidaten ausprobieren, sondern nur noch ca. 1000 hoch 3 = eine Milliarde. das reduziert die Menge doch erheblich (0.018%!).

Aber, und das fällt mir in den Diskussionen zum Thema Passwortsicherhheit immer wieder auf: Ein Angreifer muss diese Information doch haben, um effizient das Passwort zu suchen. Wenn ein Angreifer das oben genannte Passwort knacken wollte und von einem zufälligen Passwort ausginge, hätte das Passwort auch keinen Nachteil gegenüber komplexen echt zufälligen Passwörtern. Klar in dem Beispiel probiert ein Angreifer einfach zuerst mal die Silbenattacke, dauert ja nicht lange. Erst, wenn das nicht geht, wird er auf den Standardweg "alles durchprobieren" zurückgreifen.

Da ist aber noch mehr: was, wenn der Hacker weiß, dass in dem Passwort die Buchstaben wqtzoiklnjdfghbv nicht vorkommen? Dann wird aus 26 hoch neun schnell 10 hoch 9 = 1.000.000.000 (fällt euch was auf: eine Milliarde) deutlich weniger, aus einer Woche wird dann plötzlich nur noch ne Stunde.

Im Allgemeinen ist es wichtig, alles über das Passwort geheim zu halten, d.h auch die Länge. Denn wenn ein Hacker nicht weiß, dass das Passwort 9 Zeichen hat, muss er ja theoretisch alle 6, 7 und 8-Zeichenpassworte vorher auch noch durchprobieren, um alles zu haben (wenn wir davon ausgehen, dass ein brauchbares Passwort eine Mindestlänge von 6 Zeichen hat). Oder eben sogar noch kleinere Passwortlängen - wenn nötig.

Das geht auch anders herum. Ich hatte mal den Fall, dass ich bei einer Windows-Büchse mich nicht mehr einloggen konnte, weil mir das Passwort entfallen war. Windows starte ich einfach zu selten... Ich hab echt alle möglichen Passwörter ausprobiert und wollte den Rechner schon neu installieren. Bis ich drauf gekommen bin: Kein Passwort ist die Lösung... Obwohl das Passwort extrem unsicher ist (logisch, ist ja keins da) - hab ich doch echt ne Ewigkeit gebraucht, bis ich da "eingebrochen" bin.

Klar, Hacker, die was auf sich halten werden sicher diese Standardfälle auch probieren. Aber ich bin eben kein Hacker, der was auf sich hält ;-)

Wie hackt man denn? Einfach ausprobieren

Das ist auch ein großes Missverständnis. Man kann nicht einfach in der Login-Maske eine Brute-Force Attacke fahren (d.h. einfach alle Logins ausprobieren). Das klappt so nicht. Denn meistens wird eine Verzögerung eingebaut, wenn man sich beim Passwort vertippt (ist vielleicht dem ein oder anderen mal aufgefallen) und wenn man es mit den Fehlversuchen übertreibt, wird der Account deaktiviert. Zumindest passiert das bei den meisten Seiten, die was auf Sicherheit geben. Wenn ein Hacker also einen Account, sagen wir einen Webmail account, knacken will, kann er so nicht vorgehen. Und da ist nun wirklich wissen notwendig. Was der Hacker braucht ist

  1. eine Liste der logins oder zumindest einen gültigen Login. Und wenn er die Daten einer bestimmten Person will, eben dessen Login
  2. die verschlüsselte Form des Passwortes
  3. oder eine Passwortprüfsumme

was ist damit gemeint? Passwörter werden heutzutage natürlich nicht im Klartext übertragen, sondern verschlüsselt. Sonst müsste man ja einfach nur "mitlesen" (Hallo NSA!). Also muss der Hacker erst mal an eine verschlüsselte Form des Passwortes kommen. Das ist gar nicht so kompliziert, erfordert aber viel Wissen über das System.

Die verschlüsselten Passwörter sind zwar meist recht einfach zu bekommen (sie werden ja am Anfang der Kommunikation übertragen), aber dummerweise recht schwer zu knacken. Da die Verschlüsselung meistens genau das ja zu verhindern versucht.

Wenn es also kein verschlüsseltes Passwort gibt oder man nicht ran kommt, kommt man evtl. an die Prüfsumme des Passwortes ran. Passwörter werden in vielen Systemen nämlich nicht im Klartext oder verschlüsselt gespeichert (das könnte man ja irgendwie entschlüsseln), sondern es wird "nur" die Prüfsumme abgelegt. Diese Prüfsummen tauchen immer mal wieder im Internet auf, weil ein System über die "üblichen" Wege, wie Buffer overflows oder sonstige Sicherheitslöcher geknackt wurde und man (die Hacker) nichts verändern, aber dafür auslesen konnte. (nicht immer erreichen Hacker den "Heiligen Gral" - also eine root-shell auf dem Zielsystem. Häufig können sie nur einzelne, eigentlich geschützte Dateien abgreifen. Und da bieten sich solche Passwortdateien natürlich an).

Jetzt kann man Milliarden von Versuchen mit der Prüfsumme oder der Verschlüsselung machen, bis man ein Passwort gefunden hat, was die gleiche Prüfsumme ergibt oder eben die Verschlüsselung knackt. Und dann hab ich komplette Zugangsdaten. (Anmerkung: dem geneigten Leser wird aufgefallen sein, dass man ein Passwort sucht, dass die gleiche Prüfsumme ergibt. Das muss nicht zwangsläufig das selbe Passwort sein, wie das, was der User ursprünglich eingegeben hat).

Alles in Allem recht aufwändig. Hat auch nix damit zu tun, wie es häufig im TV und so dargestellt wird. Also, wenn ich so eine Passwortprüfsumme habe, dann bin ich schon mal recht weit. Ich kann ungestört so lange rumprobieren, bis ich das zugehörige Passwort gefunden habe. Jetzt kommt es stark darauf an, wie lange es dauert für einen Passwortkandidaten eine Prüfsumme zu berechnen. Das hängt ein wenig davon ab, welches Prüfsummenverfahren eingesetzt wird. Das bekannteste und auch eines der ältesten (und mittlerweile recht "unsicheren", weil zu schnell und zu klein) ist MD5. Neuere und bessere wären die auf dem SHA-Algorithmus basierenden. Neuestes Mitglied der Gruppe ist SHA3 - wenn es auch ein wenig umstritten ist (link).

Mit egal welchem dieser Verfahren, es ist auf jeden fall möglich, Millionen von Passwortkandidaten in kurzer Zeit auszuprobieren. Und welche Kandidaten ausprobiert werden, hängt wiederum davon hab, welches Wissen ich über das Passwort habe.

Will ich z.B. ein Telefon, Tablet oder Notebook knacken, wäre es notwendig, dass ich in einem unbeobachteten Moment mal Zugriff auf das Ding bekomme, damit ich auf die Daten zugreifen kann. Das ist je nach Modell und OS relativ einfach oder extrem kompliziert und zeitaufwändig. Aber es geht immer! Wenn ich das ding anfassen kann, kann ich es auch hacken - nur eine Frage des Aufwandes und der Motivation und evtl. des Geldes...

Dann lese ich irgendwie die Passwort-Prüfsummen aus und dann kann ich in aller Ruhe die Passwörter ausprobieren, die sinnvoll erscheinen. Wenn ich dem "Opfer" bei der Eingabe des Passwortes mal zugesehen habe, habe ich bestimmt eine Vorstellung davon, wie lang das Passwort ist und evtl. sogar, wie welche Buchstaben vorkommen oder sicher nicht vorkommen (bei Touchscreens geben die Fingerspuren auf dem Display gute Hinweise). Das war nur ein Beispiel und ist natürlich etwas simplifiziert - aber ich denke, man bekommt einen Eindruck.

Deswegen ist es so sinnvoll, dass man sich ein Passwort überlegt, im dem nicht nur Buchstaben vorkommen. Sonderzeichen und Umlaute sind auf jeden fall auch in ein gutes Passwort einzubauen. Je mehr buchstabenklassen man verwendet, umso besser.

Das erhöht die 26 in den Beispielen oben, und somit auch die Anzahl potenzieller Passwörter. Und: man sollte sein Passwort von Zeit zu Zeit mal ändern. Denn: wenn ich mein Passwort 3 Jahre lang nicht ändere, hat ein Hacker theoretisch bis zu 3 Jahre Zeit das Passwort zu knacken oder zu erraten. Und in 3 Jahren kann man eine Menge Passwörter ausprobieren...

Was ist denn ein gutes Passwort?

Ja, das ist genau das Problem. Was ist in dem Zusammenhang gut? Wenn ich es aus der Sicht der Security sehe, dann sollte das Passwort möglichst lang sein und möglichst zufällig und möglichst viele zeichen aus verschiedenen Zeichenklassen (Großbuchstaben, Kleinbuchstaben, Zahlen, Sonderzeichen, Punktuation, Accents, Umlaute...) beinhalten. Das kann sich aber kein Mensch merken - erst recht nicht, wenn er, wie man es eigentlich tun sollte, für jeden Zugang ein anderes Passwort benutzt.

Und da man sich so was nicht merken kann, was benutzt man dann? Geburtsdaten und so zeuch. oder ein etwas komplexeres Passwort für alles - auch nicht clever. Aber verständlich...

Das ist vermutlich auch einer der Hauptgründe, warum es Hacker (und Geheimdienste) so leicht haben, uns auszuspähen oder unsere Virtuelle Identität zu karpern oder auch nur Spam zu schicken, dass vermeindlich so gut zu mir passt. Hat man ein Passwort, hab ich alle... das ist gerade heutzutage schon als fahrlässig zu bezeichnen.

Die Lösung - Passwortstrategien...

Ein gutes passwort muss also eines sein, dass

  1. lang ist, mindestens 6 Zeichen
  2. mindestens ein Zeichen aus Jeder Klasse haben (Klein- und Großbuchstaben, Zaheln, Sonderzeichen und Punktuation)
  3. Sollte nich auf ein Wort zurückgehen.
  4. Aucn nicht LeetSpeak sein..

oder?

Ja, genau so erzwingen es einige Systemadministratoren mit den z.B. in Betriebssystemen verfügbaren Mitteln. Ob das nun die Sicherheit erhöht, sei mal dahin gestellt. Es fördert zumindest ein Bewustsein, dass so etwas wichtig ist.

Zum "Glück" - wenn man es in dieser Richtung bedenkt - gab es den NSA Skandal, denn jetzt machen sich vielleicht mehr Leute darüber Gedanken - und ich kann evtl. ein paar Denkanstöße geben.

Denn diese Security Policies, in denen festgelegt wird, dass z.B. ein Passwort monatlich geändert werden muss, führt häufig dazu, dass der Monat irgendwie im Passwort drin ist. Das ist meine Erfahrung mit solchen Policies. Knackbar sind die immer... Und wenn der Kollege fragt: "Welchen Monat haben wir denn gerade?" bevor er sich am System anmeldet, weiß man zumindest, dass der aktuelle Monat als Zahl oder Text vorkommt - das schränkt die möglichen Passwörter schon wieder um so einige Millionen ein.

Wie denn sonst? Satzbasierte Authentifizierung?

Wer sagt denn, dass ein Passwort unbedingt ein "Wort" sein muss? Klar, früher, so in den 1990ern, da war Speicherplatz so teuer, da konnte man nicht einfach ein beliebig langes Passwort erlauben, deswegen ist es ja auch nur ein Wort. Besser man benutzt heutzutage den Begriff Passphrase, also eher so was wie "Pass-Satz". Und dann kommen wir der Sache schon näher. Einen Satz kann man sich viel besser merken, als eine wüste Aneinanderreihung von Zeichen. Egal wie unsinnig der Satz ist, merken kann man sich den auf jeden Fall viel leichter. Und wenn man den Satz noch mit Sonderzeichen versieht, so hat man doch ein brauchbares Passwort, oder?

Klar hat man, denn so ein Satz hat gerne mal seine 20 Zeichen. Mit einem buchstaben- bzw. zeichenbasierten Ansatz kommt man da nicht weit. Wenn man Silben nutzt kommen wir in etwa bei der selben Komplexität raus, wie bei einem ca. 8 Zeichen Langen zufälligen Passwort!

Wie gesagt, der Satz muss nicht mal Sinn machen. Vermutlich ist es sogar besser so - sonst könnte man ja eine Wort/Satzbasierte Attacke probieren.

Nehmen wir einen sinnlosen Beispielsatz: "Der jungfräuliche, grüne Stuhl betritt eine friedliche Lampe 6x!" Hier haben wir alle Zeichenklassen vertreten, der macht überhaupt keinen Sinn ist aber leicht zu merken. Egal wie man es dreht und wendet, man hat eine Vorstellung von dem, was da gerade passiert.

Betrachten wir mal wieder die andere Seite.

  • eine zeichenbasierte Brute force Attacke wird wohl kaum sicher diese Passphrase finden. Dazu müssten ja (voausgesetzt man kennt die Länge von 66 Zeichen) 66 hoch ca. 50 Versuche unternommen werden. Die Zahl wäre echt groß... irgendwas mit 90 Stellen oder so.
  • Wie wäre es mit einem Silbenansatz? Klar, wenn der Hacker weiß, dass es sich um Deutsch handelt, könnte er versuchen die ca. 1000 Silben als Basis zu nehmen. In der Passphrase kommen 29 silben vor - d.h. er bräuchte 1000 hoch 29 versuche um nur die passenden Worte zu finden - ganz zu schweigen von den Satzzeichen und co... auch kaum praktikabel.
  • Aber er könnte doch einen Wortbasierten ansatz wählen? Auch möglich. Allerdings gibt es verflucht viele Worte ;-) Und dann müsste das auch noch so zusammengebaut werden, dass da Satzzeichen drin sind... Ich als Hacker würde mir sicher in dieser Richtung gedanken machen, aber ist schwierig anzugehen. Aber spinnen wir den Gedanken mal weiter. Gehen wir davon aus, dass es im Deutschen ca. 75000 Wörter gibt link. Wenn ich dann noch wüsste, dass in der Passphrase ca. 9 Worte vorkommen, so käme ich auf 75000 hoch 9 (eine Zahl mit 45 Stellen) mögliche Passwörter die passen. Ist schon ne Menge. Da kann ich sicherlich die rausfiltern, deren länge nicht passt, aber da bleiben schon eine Menge übrig. Wenn ich dann noch die Satzzeichen dazu packe müsste man diese Zahl noch mal mit 10 Multiplizieren (an 10 Stellen könnte ja ein Satzzeichen stehen). Alles in allem ne recht große Zahl, oder?

Das klingt doch wirklich vielversprechend, warum also nicht gleich so eine Passphrase benutzen? Naja... 66 Zeichen müssen auch eingegeben werden. Nicht jeder kann das 10 Finger System und tippt das ganze in einer Sekunde. Und auf einem Touch-Screen ist das ganze noch ungleich schwieriger, da ist es fast unmöglich so viele Zeichen ohne Fehler zu tippen. Passphrases sind sicher ne gute Lösung, aber nicht für jeden Anwendungsfall.

Wie wäre es mit Biometrie?

Klar, gerade mit dem aktuellen iPhone 5s und seinem Fingerabdrucksensor kommt diese Diskussion wieder auf. Stimmerkennung und Gesichtserkennung zählt auch zu den Biometrischen Verfahren. Warum hat sich das noch nicht durchgesetzt?

Das Problem mit Biometrischen Verfahren ist häufig die Ungenauigkeit und, dass man zwar erkennen kann, ob derjenige, der da vor der Kamera steht, Herr Müller ist, oder nicht. Nur zuverlässig erkennen kann man Herrn Müller deswegen noch nicht. Und wenn Herr Müller nen eineiigen Zwilling hat wird die Sache schnell extrem kompliziert.

Und die meisten Biometrischen Systeme kann man mit einer Audioaufzeichnung oder einem Foto austricksen. Bei Fingarabdrucksensoren ist das ganze schon schwieriger und mit Bastelei verbunden. Aber auch das geht.

Biometrische Verfahren werden mehr und mehr Einzug in die Technik halten, aber vermutlich nicht der Weisheit letzte schluss sein. Insbesondere wenn diese Daten dann irgendwo abgelegt werden.... Datenschutz ist da noch mal wichtiger...

Passworttools - Gefahr oder Segen?

Da man wohl in absehbarer Zeit nicht ohne Passwörter oder Ähnliches auskommen wird, kommt die Frage: wie soll ich das einerseits sicher machen und andererseits in mein Hirn bekommen?

Da helfen Passwort-Apps, wie z.B. 1Password. Diese speichern Passwörter z.B. für Webformulare oder WLan-Zugänge in einer eigenen Datenbank verschlüsselt ab. Gesichert wird das ganze nur durch ein einziges Passwort (oder besser eine Passphrase) mit der man dann Zugriff auf alle eine Passwörter Bekommt.

Apple hat so was auch mit der "Schlüsselbundverwaltung" und jetzt neu der Passwortsynchronisation über die Cloud.

Hey, cool, damit ist doch alles gelöst... Oder?

Naja, theoretisch schon. Aber ein findiger Hacker könnte ja jetzt auf die Idee kommen, gar nicht das Passwort deines EmailAccounts zu knacken, sondern einfach den Store von 1Password. Wenn ihm das gelingt bist du ganz schön angeschmiert. Dann müssen alle(!) dort gespeicherten Passworte geändert werden - und finde das mal raus.

Eine gruselige Vorstellung. Deswegen sollte man solche tools wirklich nur unter folgenden Bedingunen verwenden:

  1. man sollte sich mal testberichte ansehen, wie die App die Daten verschlüsselt und was die Experten dazu so sagen
  2. Welche Firma steckt dahinter? Ist die Vertrauenswürdig?
  3. Wenn ich die Daten zwischen verschiedenen Geräten Synchronisiern möchte, geht das hoffentlich auch offline? Z.b. per Wlan oder bluetooth?
  4. Wenn ich online Synchronistation nutze, dann sollte ich mir 2x überlegen, ob ich da auf ein Proprietäres protokoll setzen sollte. Am besten wäre es, die App unterstützt vertrauenswürdige drittanbieter (sofern man die so nennen kann) wie DropBox oder iCloud.
  5. Ich sollte mir auch hier überlegen, welche Daten ich da ablege. Und mir bewusst sein, wie gefährlich es wäre, wenn jemand darauf zugriff bekäme
  6. Hier sollte ich eine Passphrase benutzen!

Auch wenn das den Komfort etwas einschränkt, ich denke, das ist es wert... Dann kann man auch solche Tools guten Gewissens einsetzen - aber nicht Bedenkenlos!

Verschlüsselung hilft, oder?

Verschlüsselung wird ja jetzt immer wieder als die Lösung für alle Probleme mit der Privatsphäre und Datensicherheit genannt. Das ist zwar richtig, aber Verschlüsselung alleine bringt zunächst mal nix, wenn der Schlüssel, mit dem ich verschlüssele, innerhalb von Sekunden zu knacken ist.

Die ganze Passwortdiskussion von oben ist auch für das Verschlüsselungsthema wichtig. Denn die beste Verschlüsselung nützt nix, wenn der Schlüssel "1234" ist.

Deswegen noch mal: auch wenn Daten verschlüsselt irgendwo abgelegt werden (wie in PasswortManagern) ist das per se noch nicht vor Fremdem Zugriff geschützt!

und nu?

Naja... wie viel Sicherheit man nun für seine Daten benötigt muss jeder für sich selbst entscheiden. Wer sich mit einem vierstelligen Code auf dem Telefon gut fühlt, der kann das weiterhin nutzen. So jemand wird vermutlich sein Telefon etwas strenger beäugen, als jeman, der sein Telefon mit einem 20-Stelligen Code gesichert hat.

Security kostet immer irgendwas. Meistens eine Kombination aus Komfort, Geld und Zeit. Und jeder muss entscheiden, wie viel von jedem ihm die sicherheit der Daten wert ist.

Nur so ein Beispiel: Ein Iris-Scanner vor dem Klo ist sicherlich wenig sinnvoll.... Aber der Banktresor sollte mit mehr geschützt sein als mit einem Fahrradschloss...


Kategorie: Datenschutz

Porno-Abmahnungen und der Datenschutz

Mi, 15. 05. 2013 - Tags:

Das was jetzt mal wieder passiert ist, ist schon beinahe als Super-GAU zu bezeichnen und sollte eigentlich allen Datenschutz-Kritikern die Augen öffnen. Was ist passiert?

In den letzten Wochen gingen mehrere 10.000 Abmahnungen (vermutlich ca. 30.000 und es sollen noch mehr werden) wegen Urheberrechtschutzverletzungen im Internet an ahnungslose User. Das an sich wäre ja nix besonderes, allerdings ist der Grund diesmal, dass man sich das urheberrechtlicht geschützte „Werk“ auf einer Porno-Streaming-Plattform (redtube.com) angesehen haben soll. Bisher galt das Streamen nicht als Verbreiten von illegalen Inhalten (da man ja nichts verbreitet und sich die Datei auch nicht herunterlädt) und man konnte dem Benutzer auch normalerweise keine Klage bzw. Abmahnung ins Haus schicken. Es sei denn, es ist wirklich eindeutig, dass das ganze illegal ist (wie z.B. wenn man aktuelle Kinofilme oder Serien vor dem Deutschen TV-Start ansieht).

Hier ist der Sachverhalt anders. Redtube wird wohl von der Porno-Industrie als Werbeplattform verwendet, die dort angepriesenen Videos sollten also rechtefrei sein oder der Uploader tritt das Recht an redtube.com zum Zwecke des Streamens ab – wobei er sicherlich auch irgendwo zustimmen muss, dass er im Besitz dieser Rechte ist. Es war wohl in keiner Weise ersichtlich, ob und warum es sich bei den genannten Machwerken um urheberrechtlich geschütztes Material handeln sollte. Es unterschied sich nicht weiter von den weiteren Angeboten der Streaming Seite. Normalerweise auch nichts besonderes. Der Rechteinhaber verlangt die Herausgabe desjenigen, der das Video hochgeladen hat und kann sich an den wenden. Das wäre aber bei weitem nicht so lukrativ wie mehrere 10.000 Leute abmahnen, von denen jeder mind. 200€ Zahlen soll.Roblox HackBigo Live Beans HackYUGIOH DUEL LINKS HACKPokemon Duel HackRoblox HackPixel Gun 3d HackGrowtopia HackClash Royale Hackmy cafe recipes stories hackMobile Legends HackMobile Strike Hack

Es wurde wohl ein Antrag auf die Herausgabe der Privatadressen beim Landgericht Köln beantragt, und das hat man so formuliert, als handele es sich um eine Tauschbörse, nicht eine Streamingplattform. Auch die nachträgliche Begründung, beim Streamen hätte man am Ende die gesamte Datei auf der Platte, ist doch sehr fadenscheinig. Denn, zum einen wird die Datei beim Steramen nicht vollständig abgelegt und zum zweiten ist es wohl doch eher unwahrscheinlich, dass sich ein Konsument dieser Filme, diese wirklich bis zum Ende ansieht. Ich kann mir nicht vorstellen, dass die Handlung da besonders spannend ist, ihr versteht.

Wie kommen die netten Rechtsanwälte denn überhaupt an die IP-Adressen? Von Redtube haben sie die nicht, die haben sich sehr von dem Vorgehen distanziert und wollen ihrerseits eine Klage gegen die Rechtsanwaltskanzlei anstreben.

Die Gerüchte darüber sind schon wirklich haarsträubend und wenn nur ein Bruchteil davon stimmt, ist das schon wirklich extrem zwielichtig. So ist von extra dafür geschriebenen Viren bzw. Trojanern die Rede. Aber die weit wahrscheinlichere Variante ist die, dass man ein Werbebanner für das IP-Tracking genutzt hat. Das bedeutet, man kann auf Redtube einen Filmausschnitt bzw. Trailer hochladen und kann den dann mit einem eigenen Werbebanner versehen, damit die Leute das Filmchen dann im Idealfall auch kaufen können. Dieses Werbebanner liegt dann bei demjenigen, der die Werbung schaltet, also auf einem anderen Server als Redtube. Und auf dem eigenen Server kann ich natürlich alle IP-Adressen mit protokollieren, die darauf zugreifen.

Das ist insofern zwielichtig, als dass dieses Banner ja wissentlich von dem, der das Video hochgeladen hat, auch irgendwie eingestellt werden. Und wenn es so war, dann muss er gewusst haben, dass es sich bei dem Video um ein urheberrechtlich geschütztes Werk handelt – Warum sollte er dann solch ein Banner schalten? Vor allem stelt sich wohl raus, dass das Video schon seit geraumer Zeit dort gestreamt werden durfte, aber erst kürzlich das Banner geschaltet wurde. Aber anstelle bei Redtube anzumahnen, dass der Film da zu sehen ist und somit die Löschung des Films von deren Servern zu verlangen, wird lieber ein Werbebanner geschaltet, welches es dann möglich macht, 10.000e User abzumahnen?

Erstaunlich ist diese Grafik (hier der original-Post dazu), welche deutlich zeigt, dass die Zugriffe auf die angemahnten Inhalte zufälligerweise genau in der Zeit sprunghaft angestiegen sind, in der die angeblichen Urheberrechtsverstöße stattgefunden haben. Und dass zufälligerweise genau 2 Tage zuvor die Domain für das Werbebanner gekauft wurde…. Zufall?

Wie gesagt, es war für den User wohl nicht ersichtlich, dass es sich um ein illegal zum Streamen freigegebenes „Werk“ handelt. Und eigentlich hätten die Adressen der User gar nicht rausgegeben werden dürfen.

Was hat das jetzt mit Datenschutz zu tun?

Es beweist wieder ein mal, dass Daten in den falschen Händen immer irgendwie zu Geld gemacht werden können oder zumindest eine Menge Geld kosten können. Denn, selbst wenn sich herausstellen sollte, dass diese Abmahnungen alle nicht rechtens waren (zum Glück sieht es momentan so aus – siehe auch hier) und man die Adressen zu den IPs gar nicht hätte rausgeben dürfen, selbst dann bleiben die Betroffenen auf Kosten von mehreren Hundert Euro sitzen! Die können sie sich zwar theoretisch vom Verursacher (also dem Rechteinhaber) wieder holen, aber das geht nur, wenn man es den Schdandensersatz einklagt. Und es ist fraglich, ob das wirklich so einfach geht, denn der Rechteinhaber sitzt wohl in der Schweiz. Und internationale Klagen sich teuer. Und den eigenen Rechtsanwalt muss man vorher bezahlen… Und wenn wirklich, wie es scheint eine Sammelklage gegen diese Abmahnungen erfolg haben sollte, dann ist die Firma leider auch schnell pleite und die Geschädigten bekommen dann auch nichts oder nicht viel.

Ich bin auch der festen Überzeugung, dass das ganze nur deswegen probiert wurde, da es sich um Schmuddelkram handelt. Da werden bestimmt schon 1000e bezahlt haben, bevor das ganze überhaupt öffentlich geworden ist – eben um die Öffentlichkeit zu meiden und man will ja nicht mit so einem Kram in Verbindung gebracht werden. Und da kann man wohl eine Menge Geld machen…

Das ganze ist eine One-Shot-Aktion, man versucht schnell mit der Unwissenheit und der Peinlichkeit der Geschichte Geld zu machen, wohl wissend, dass man sich da auf dünnem Eis bewegt. Die haben aber dennoch sicherlich schon einige Hunderttausend Euro eingenommen.

Kurz zusammengefasst: Weil jemandem die Daten peinlich sind und er nicht will, dass es öffentlich wird, wird bezahlt. Klingelt es da? Ich hatte ähnliches als Schreckensszenario in einem meiner Posts beschrieben, und nun ist es Realität geworden.

Ja, jetzt kommt das Hammer-Argument: Ich gucke keine Pornos im Internet.

Damit ist natürlich alles gut, und es ist nur das Problem einzelner.

ARG!!!! Darum geht es nicht! Es geht darum, dass du, einfach nur weil du einen falschen Link angeklickt hast (denn genau darum handelt es sich), ohne zu wissen, was sich dahinter verbirgt, plötzlich Schadenersatz zahlen musst. Wenn das wirklich legitimiert wird, ist das Internet tot! Dann kann man sich nicht mehr frei bewegen, kann keinen Link mehr anklicken, ohne vorher zu wissen, was sich dahinter verbirgt. Man stelle sich das mal vor, dass man auf einen Link klickt, auf der Seite wird ein Zitat von einem Buch verwendet und der Buchautor will das nicht – Zack, klage am Hals! Und selbst wenn vor Gericht geht und evtl. sogar Gewinnt – man muss die Kohle erst mal vorstrecken (kann gar nicht jeder) und dann muss man auch noch im Nachhinein auf Schadenersatz klagen, bei dem man evtl. auch verliert aber auf jeden Fall wieder Kosten vorstrecken muss.

Ich finde es wirklich erschreckend, wie schlampig da auch auf Seiten der Gerichte gearbeitet wurde, wie einfach jemand, mit genug „Kreativität“ an die Privatadressen von sorglosen Internetusern kommt. So etwas hätte es gar nicht geben dürfen. Der Schaden der Beteiligten ist auf jeden Fall da, denn ohne Rechtsanwalt kommt man aus der Sache leider nicht wieder raus!

Und die Politik will allen Ernstes gerade wieder die Vorratsdatenspeicherung einführen? Das ist super, dann braucht man gar keine Werbebanner mehr, sondern kann sowas sicherlich auch rückwirkend machen und sich so bereichern…. Ich glaube es hackt!

Und um das gleich klar zu stellen: Nein, ich bin nicht betroffen 😉

Update: es scheint sich zu bestätigen, dass da wirklich eine absichtliche Täuschung versucht wurde. Was das ganze natürlich in noch schlimmeres Licht rückt und wieder einmal zeigt, wie wichtig es ist, die Privatsphäre zu schützen. Die Staatsanwaltschaft ermittelt wohl zurecht – alle Betroffenen sollten sich juristischen Rat holen!


Kategorie: Computer

Whatsapp und Datenschutz! Sorglosigkeit in Deutschland...

Sa, 04. 05. 2013 - Tags:

Ich bekomme ja immer wieder mal zu hören, wie blöd das doch ist, dass ich kein WhatsApp benutzte und warum denn nur, denn schließlich würden das doch alle machen. Ja, und wenn alle aus dem Fenster springen, springe ich noch lange nicht hinterher. Warum benutze ich WhatApp nicht? Ganz einfach, ich habe was gegen undurchsichtige Firmen, die Daten von mir wollen. Insbesondere Daten über dritte, die nicht gefragt werden. Im WDR wurde in der Sendung Markt kurz in einem Bericht vor WhatsApp gewarnt und es wurde gezeigt, wie sorglos die Leute mit ihren persönlichen Daten umgehen: http://www.wdr.de/tv/markt/sendungsbeitraege/2013/1202/whatsapp.jsp

Hey, aber es ist doch umsonst… Gut WhatsApp verlangt jetzt 1€ für ihren Service. Was lustigerweise bei einigen in meinem Bekanntenkreis nie abgebucht wurde und auch nicht wirklich funktioniert hat. Das spricht nicht gerade dafür, als würde man bei WhatsApp die Zahlung besonders ernst nehmen. Wir oft muss ich es eigentlich noch sagen: keine Firma, sei sie noch so klein oder noch so groß, macht etwas umsonst! Da ist immer irgend ein Hintergedanke. Und wenn das im Geschäftsmodell nicht zu erkennen ist (wie bei Viber und WhatsApp), dann sollte man vorsichtig sein. Es ist echt der Hammer, wie dämlich die Leute reagieren, wenn es quasi was umsonst gibt. Ich muss da an eine Folge „Pinky und der Brain“ denken…

Wir nutzen die größte Macht im uns bekannten Universum…. FREE T-SHIRTS!

Anscheinend geht das auch mit unsicheren Apps, die uns ausspionieren. Und das schlimmste ist, viele Länder reagieren bereits. WhatsApp wird verklagt, gezwungen Daten zu löschen oder sonst was – und in Deutschland passiert nix weil „Das Server im Ausland sind, da können wir nix machen“. Ja geht’s noch? Die NSA sitzt auch im Ausland, machen wir deswegen nix?

Aber dass die Politik nicht reagiert ist nur eine Reflexion der Sorglosigkeit der Bevölkerung. Es ist den Menschen einfach egal, wenn jemand ominöses irgendwo in USA ihre Chatnachrichten liest, alle persönlichen Daten bekommt. Dabei ist es sogar egal, wenn die Firma ihren Firmensitz sogar geheim hält – was nun wirklich nicht gerade seriös ist. In dem Zusammenhang sind sich WhatsApp und Viber erstaunlich ähnlich… Dabei ist es technisch ohne Weiteres möglich, die Daten so zu versenden, dass nicht mal WhatsApp diese wieder lesen kann. So etwas macht Microsoft mit Skype und Apple mit iMessage. Da könnte man nur Daten abgreifen, wenn man auf dem Endgerät selbst spioniert, was ungleich schwieriger zu bewerkstelligen ist, als das zentral über den Server einfach abzugreifen. (immer vorausgesetzt, dass die Verschlüsselung dieser Chat-Programme wirklich end-to-end ist).

Sowohl WhatsApp als auch Viber reagieren immer nur sehr zögerlich auf Sicherheitsbedenken und halten sich überhaupt sehr bedeckt was das betrifft. Und nicht nur beim Thema serverseitiges Ausspionieren ist Whatsapp eher zwielichtig, auch die offensichtlichen clientseitigen Sicherheitsprobleme sind gravierend. So kann man die „Verschlüsselung“ ohne Probleme aushebeln (siehe hier) und man kann auch anonym Nachrichten verschicken oder aber man kann einfach als jemand anders diese Nachrichten verschicken – besonders kompliziert ist das bisher wohl nicht published here.

Es wird dann auch häufig das Argument gebracht, dass WhatsApp in ihrer AGB aber ausschließen, dass sie so was machen. Bestimmt. Gaaanz sicher.

Das mag für Firmen in Deutschland vielleicht relevant sein, aber für eine Firma in den USA (oder Israel? Oder Russland?), deren Firmensitz nicht mit 100%iger Sicherheit bestimmt werden kann, ist das wohl auch nur Blendwerk. Klar, ich könnte sie verklagen, wenn sie gegen die AGB verstoßen… wie ich auch die NSA verklagen könnte. Das Problem damit ist, dass die Firma, die ein Geheimnis von sich macht, die Daten speichern kann und (das steht auch in den AGBs – eine gewisse Zeit lang angeblich auch speichern muss) damit Zugriff auf die Daten bekommt. Und was in dieser Zeit mit den unverschlüsselten Informationen geschieht steht da nicht. Und die Ausrede, man braucht die Kontaktinformationen – natürlich NUR die Telefonnummer, das ist ja fast nix – um abgleichen zu können, ob jemand in WhatsApp bekannt ist, ist mehr als fragwürdig. Wozu müssen diese Infos dann auf den Server gepackt werden? Es gibt kaum eine bessere Möglichkeit, jemanden zu identifizieren als über seine Telefonnummer. Die meisten ändern diese nämlich selten oder gar nicht.

Das sind alles Probleme, die sich lösen lassen. Wenn es aber keinen der Nutzer stört, wird es wohl auch niemanden geben, der sich darum kümmert. Und warum das alles? Das ist ja sowieso das, was ich nicht verstehe: es gibt wirklich 100e Alternativen, die von nicht so zwielichtigen Firmen kommen, wie WhatsApp und Viber. Allen voran natürlich iMessage und Skype. Aber selbst GoogleChat oder FacebookChat ist besser, denn da kennt man die Firmen dahinter und kann notfalls jemanden greifen. Und der größte Hohn an der Sache: Die meisten haben eine SMS Flat! Aber SMS sind uncool, oder was? Selbst die sind sicherer und werden nicht zentral gespeichert (fast: der Netwerkbetreiber speichert die Wohl für eine Zeit ab, aber das ist in Deutschland einigermassen reglementiert).

Auf mein Telefon kommt sicher kein WhatsApp oder Viber drauf – wer mich erreichen will, der kann auch über SMS erreichen. Oder, ein ganz seltsamer Gedanke, anrufen! Und wem das nicht wichtig genug ist, der braucht mich eben nicht zu kontaktieren – Auch ne Art natürliche Selektion 😉      


Kategorie: Allgemeines

Wer ist Stephan Bösebeck?

Fr, 19. 04. 2013 - Tags: about

Ursprünglich veröffentlicht auf: https://boesebeck.name

Wessen Blog ist das hier? Ja, wer bin ich... diese Frage ist erstaunlich schwierig zu beantworten, dummerweise. Mein IT-Werdegang ist auf hier im Blog unter Meine IT Geschichte nachzulesen. Da gibt’s es nicht viel mehr zu sagen. Privates wird es hier nur wenig geben, deswegen nur noch mein beruflicher Werdegang in aller Kürze:

  • Nach dem Abi (und einer recht dämlichen Episode bei der Bundeswehr - aber das ist eine andere Geschichte) hab ich Informatik an der Uni Passau Studiert
  • Abschluss mit Diplom
  • Da mir nur wenig bis kein BaFöG zustand und meine Eltern auch nicht sonderlich viel beisteuern konnten, habe ich schon während des Studiums angefangen zu arbeiten
  • Abgesehen von einigen Studentenjobs (z.B. beim Computer Discounter ESCOM - leider pleite mittlerweile, hab da aber viel gelernt) war ich ab dem Jahr 1995 als IT-Consultant unterwegs
  • Ich habe in dieser Zeit auch einige Zertifizierungen gemacht: Sun Certified Trainer, Certified Java Programmer, LPI Zertifizierung, Certified C++ Programmer, Certified Macro4 Trainer, usw. - Ich könnte vermutlich ganze Zimmer mit dem Zeuch tapezieren
  • Kunden von mir waren unter anderem: IBM, Sun, Dresdner Bank, Deutsche Bank, HP,  Unilog Integrata, Ixos Training, Goethe Institut...
  • Ich hab mich auf die Technologien Linux/Unix und Java spezialisiert. Deswegen auch die Zertifizierungen in den Bereichen
  • Ich habe vor allem für SUN und Integrata Java Schulungen gemacht, angefangen von Beginner bis hin zu Advanced Java Programmer Zertifizierungs Schulungen
  • Ähnlich sah es mit Linux Schulungen aus, u.A. für IBM. Angefangen von Beginner-Schulungen bis hin zu Advanced Clustering und Firwalling Themen -Natürlich gab es auch Schulungen in anderen Bereichen rund um Unix: Perl, TCL/Tk und noch ein paar andere Exoten (Python war damals noch echt exotisch emoji people:smirk)
  • Das Wissen für die Schlulungen hab ich mir in vielen, leider meist recht kleinen Projekten angeeignet. Da gab es eine Menge klein- und mittelständische Unternehmen, für die ich Software Entwickelt habe. Oder Zuarbeit geleistet habe bei größeren Projekten.
  • Da ich die Projekte sehr häufig dann doch nicht allein realisieren kann, wurde ich sehr schnell auch in die Rolle des Projektleiters / ~managers "gedrückt". Als Ansprechpartner für die Kunden, als Auftraggeber für Subunternehmer etc.
  • Da ich als Freiberufler allein kaum eine Chance hatte, mal ein großes Projekt zu leiten oder zu realisieren hab ich mich entschlossen, fest angestellt zu arbeiten. In dem Fall habe ich bei Softlab als Projektleiter angefangen
  • Dort durfte ich einige größere Projekte Realisieren (naja, zumindest größer als das vorher). Unter Anderem für MAN und BMW
  • Ich wollte mehr Management-Erfahrung sammeln und wurde dann von Acronis "abgeworben". Ich hab dort als "Manager Training & Consulting EMEA" angefangen. und musste dort die Trainings-Abteilung aufbauen. Zunächst nur für Deutschland, später für EMEA, dann weltweit.
  • Die Trainings-Verwaltung und das Trainings-Management ist dann mehr oder minder nach USA umgezogen, weshalb ich dann "Head of Engineering" bei Holidayinsider.com wurde. Leider hat HRS uns aufgekauft und mittlerweile gibt es Holidayinsider auch nicht mehr
  • Ich war dann etwas mehr als ein Jahr Head of Technology bei Simplesystem GmbH & Co Kg - das war aber leider nicht so ganz das richtige
  • der Nächste Job ist schon fix, werde ich aber erst hier posten, wenn ich da angefangen habe emoji people:smile


Kategorie: Allgemeines

zum Thema Vorratsdatenspeicher...

Mi, 14. 09. 2011 - Tags: tweet

Ursprünglich veröffentlicht auf: https://boesebeck.name

zum Thema Vorratsdatenspeicherung: http://t.co/vrQAaPk

Suchergebnis: 15

<< 1 ... >>