Es sind noch nicht einmal 2 Wochen seit der AWS re:Invent-Konferenz vergangen und ich freue mich schon auf die Konferenz im nächsten Jahr! Es war eine sehr interessante Woche mit vielen aufregenden neuen Ankündigungen von Amazon. Ich habe von technischer Seite viele neue Dinge gelernt und viel Spaß gehabt :)

Jetzt konzentrieren wir uns auf meine Lieblingsthemen der re:Invent in diesem Jahr, den Dienst AWS Lambda und den neuen Trend, die serverlose Architektur. Mit “serverlos” meine ich keine bestimmte Infrastruktur, Bereitstellung oder dass der Server Ihren Code (der als Lambda-Funktion umschlossen ist) ausführen muss, um mit bestehenden AWS-Diensten als Antwort auf native AWS-Ereignisse zu kommunizieren. Es grenzt an Zauberei! Ganz abgesehen davon, dass AWS Lambda Code in JavaScript, Java und Python unterstützt und diese Funktionen zustandslos sind. Dies bedeutet, dass eine Skalierung und viele Kopien der gleichen Funktion rasch gleichzeitig ausgeführt werden können.

Hier ist ein Beispiel, das die Leistung dieses Diensts und die vielen Möglichkeiten zeigt, die AWS Lambda und die Nuxeo Platform bieten. Eine gängige Bereitstellung ist ein Nuxeo-Server, der in einer EC2-Instanz mit Amazon S3 als Binary-Manager konfiguriert ist.

In dieser Konfiguration wird ein Dokument, das auf der Nuxeo Platform und dem zugehörigen Blob (falls vorhanden) erstellt wurde, in S3 gespeichert. Wenn Sie eine Datei hochladen, wird sie standardmäßig zuerst auf die Nuxeo Platform hochgeladen. Die Plattform lädt sie dann auf S3 hoch. Mithilfe von AWS Lambda können Sie die Datei gleich auf S3 hochladen. Das Dokument, das auf diesen Blob verweist, wird automatisch in der Nuxeo Platform erstellt.

Anwendungsfall

Denken Sie sich in folgende Situation hinein: Sie haben gerade begonnen, die Nuxeo Platform zu verwenden und wollen einen Massenimport durchführen, um alle Dokumente aus Ihrem alten System hochzuladen.

Wir führen es schrittweise durch. Da es sich hierbei nur um eine Machbarkeitsstudie handelt und ich mich auf die Funktionalität konzentriere, verwende ich die Standardkonfiguration, keine Verschlüsselung in S3, keinen mehrteiligen Upload und Standardauthentifizierung mit einem Standardbenutzer, um das Dokument auf der Nuxeo Platform zu erstellen.

Dies ist meine aktuelle Bereitstellung (der S3BinaryManager-Marktplatz ist installiert und konfiguriert):

Nuxeo und S3

Configure the bucket

Standardmäßig gibt es S3 Bucket-Ereignisse, wenn Objekte erstellt, geändert oder aus einem Bucket entfernt werden. Eine der tollsten Funktionen an Lambda ist die native Integration mit diesen Mitteilungen, sodass wir eine Lambda-Funktion für eine beliebige Ausführung konfigurieren können. Und diese Funktion muss nur das Dokument auf der Nuxeo Platform erstellen.

Im Wesentlichen ist dies unser Ziel: Eine Lambda-Funktion, die benachrichtigt wird, wenn ein Objekt in unseren Bucket hochgeladen wird. Diese Funktion ruft dann einen Vorgang hervor, um auf der Nuxeo Platform ein Dokument zu erstellen, das auf diesen bestehenden Blob verweist.

Lambda-Funktion

Erstellen der Lambda-Funktion

In der AWS-Konsole erstellen wir eine neue Lambda-Funktion aus einer vorhandenen Node.js-Vorlage. Eine Vorlage ist eine von Amazon bereitgestellte Funktionsvorlage, die schon eine beispielhafte objekterstellte Amazon S3-Funktion bereitstellt. Man muss dabei lediglich berücksichtigen, dass sich die Funktion in derselben AWS-Region wie der S3-Bucket und die EC2-Instanz befinden muss.

Hier die Ablaufschritte. Gehen Sie zu AWS Lambda und erstellen Sie eine neue Funktion.

  1. Wählen Sie bei der Auswahl der Vorlage die Vorlage s3-get-object.
  2. Bei der Konfiguration der Ereignisquellen ist S3 schon vorausgewählt. Wählen Sie Ihren Bucket (bei mir heißt er “mariana”) und wählen Sie als Ereignistyp “Object Created (All)”.
  3. In der Funktion “Configure” ist die Laufzeit schon node.js. Sie können den Speicher bei 128 MB belassen und die Zeitüberschreitung auf 5 Sekunden erhöhen. Für die IAM-Rolle wählen Sie die bestehende Rolle lambda_s3_exec_role, da diese Rollen über die erforderlichen Berechtigungen für die durch die Funktion ausgeführten AWS-Funktionen verfügt.

Das war’s dann fast schon! Jetzt müssen Sie nur noch Ihren benutzerdefinierten Code hinzufügen, um das Dokument auf der Nuxeo Platform zu erstellen. Wie Sie im bestehenden Code sehen, sind manche Protokolle bereits aktiviert. Die Ausgabe dieser Protokolle kann in CloudWatch gesehen werden. Öffnen Sie einfach den Unter-Tab “Monitoring”, wo Sie nützliche Statistiken wie die Anzahl der Aufrufe oder die Dauer finden. Klicken Sie dann im CloudWatch-Link auf “View logs”. Abschließend haben Sie Folgendes:

Erstellen einer Lambda-Funktion

Save and test Lambda function

Hinzufügen von benutzerdefiniertem Code, um das Dokument auf der Nuxeo Platform zu erstellen.

Benutzerdefinierte Nuxeo-Operation:

Das ist der knifflige Teil. Wir können nicht einfach die Operationen Create.Document oder FileManager.Import aufrufen, denn beide erwarten die Datei als Parameter. Also müssen wir eine benutzerdefinierte Operation schreiben, der das Dokument erstellt, das auf den vorhandenen Blob verweist. Der S3BinaryManager benötigt den Digest der Datei (der Standardalgorithmus ist MD5) und dies muss einer der Parameter sein, die von der Operation zusammen mit dem Titel, dem Content Typ und der Länge des Blobs erwartet werden.

Aus diesem Grund müssen wir vorsichtig sein und den Digest als Objektschlüssel verwenden, wenn wir das Objekt auf S3 hochladen. Wir müssen auch daran denken, den Dateinamen weiterzugeben.

Hier ist der Code:

@Operation(id = CreateDocumentFromS3Blob.ID, category = Constants.CAT_DOCUMENT, label = "Create", description = "")
public class CreateDocumentFromS3Blob {

    public static final String ID = "CreateDocumentFromS3Blob";

    @Context
    protected CoreSession session;

    @Param(name = "filename")
    protected String filename;

    @Param(name = "mimeType")
    protected String mimeType;

    @Param(name = "digest")
    protected String digest;

    @Param(name = "length")
    protected Long length;

    @OperationMethod(collector = DocumentModelCollector.class)
    public DocumentModel run(DocumentModel doc) throws Exception {
        if (filename == null) {
            filename = "Untitled";
        }
        DocumentModel newDoc = session.createDocumentModel(doc.getPathAsString(), filename, "File");
        newDoc = session.createDocument(newDoc);
        StorageBlob sb = new StorageBlob(new LazyBinary(digest, Framework.getLocalService(RepositoryManager.class).getDefaultRepositoryName(),
                (CachingBinaryManager) Framework.getLocalService(BinaryManagerService.class).getBinaryManager(                     Framework.getLocalService(RepositoryManager.class).getDefaultRepositoryName())), filename,mimeType, null, digest, length);
        newDoc.setPropertyValue("file:content", sb);
        newDoc.setPropertyValue("dc:title", filename);
        return session.saveDocument(newDoc);
   }
}

Interessant an diesem Code ist, dass wir eine LazyBinary mit dem gegebenen Digest erstellen und als Eigenschaft file:content einrichten.

Hinzufügen von benutzerdefiniertem Code zur Lambda-Funktion:

Nehmen wir an, dass wir diese benutzerdefinierte Operation auf unserer Nuxeo Platform in der EC2-Instanz ausführen. Wir müssen den benutzerdefinierten Code hinzufügen, um ihn von unserer Lambda-Funktion aus aufzurufen.

Für Demozwecke nehmen wir einfach Administrator/Administrator als Benutzer, der die Operation aufruft, und erstellen das Dokument in seinem persönlichen Arbeitsbereich (die Eingabe der Operation ist in der ID dieses Dokuments hartcodiert).

Wie schon oben erwähnt, erwartet der S3 BinaryManager, dass der Digest des Blobs als Objektschlüssel im Bucket verwendet wird, das heißt, wir müssen das Objekt mit diesem Schlüssel hochladen. Da wir auch den Titel des Dokuments benötigen, können wir benutzerdefinierte S3-Metadaten für die Übergabe verwenden.

Lambda-Funktion:

var aws = require('aws-sdk');
var s3 = new aws.S3({
    apiVersion : '2006-03-01'
});
var http = require('http');
var crypto = require('crypto');

var options = {
host : '52.26.252.66',
port : '8080',
method : 'POST',
path : '/nuxeo/site/automation/CreateDocumentFromS3Blob',
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json+nxrequest'
},
auth : 'Administrator:Administrator'
};

exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(event, null, 2));

    var bucket = event.Records[0].s3.bucket.name;
    var key = event.Records[0].s3.object.key;

    var params = {
    Bucket : bucket,
    Key : key
    };

    s3.getObject(params, function(err, data) {
        if (err) {
            console.log(err);
            var message = "Error getting object " + key + " from bucket " + bucket + ". Stellen Sie sicher, dass sie vorhanden ist und Ihr Bucket sich in derselben Region wie diese Funktion befindet.";
            console.log(message);
            context.fail(message);
        } else {

            //Nuxeo erwartet, dass der Schlüssel der Digest der Datei ist
            // var digest = crypto.createHash('md5').update(data.Body).digest("hex");
            var title = data.Metadata.title !== undefined ? data.Metadata.title : key;
            //console.log('title :', data.Metadata.title);

            //die Eingabe ist die ID des übergeordneten Dokuments
            var postData = JSON.stringify({
            "input" : "f04453f9-de1c-4a8d-9956-add074069813",
            "params" : {
            "filename" : title,
            "mimeType" : data.ContentType,
            "digest" : key,
            "length" : data.ContentLength
            }
            });

            var req = http.request(options, function(res) {
                res.on('data', function(response) {
                    console.log('Nuxeo response:' + response);
                    context.succeed('succeed');
                });

                res.on('end', function(response) {
                    context.succeed('end');
                });

            });
            req.write(postData);
            req.end();
        }
    });
};

Das war’s dann schon!

Sehen Sie es an einem konkreten Beispiel:

1. Über die Befehlszeile lade ich mein Foo Fighters-Ticket (übrigens ein tolles Konzert ! : )) in meinen S3-Bucket hoch, indem ich den Titel als benutzerdefinierte Metadaten übergebe:

Marianas-MacBook-Pro:opt mariana$ md5 /Users/mariana/Downloads/FooFigthers.pdf
MD5 (/Users/mariana/Downloads/FooFigthers.pdf) = 1a29c592b09ee7725415efa354907426
Marianas-MacBook-Pro:opt mariana$ aws s3api put-object --bucket mariana --key 1a29c592b09ee7725415efa354907426 --body /Users/mariana/Downloads/FooFigthers.pdf --content-type application/pdf --metadata title=FooFighters.pdf

Die Rückantwort lautet:

{
    "ETag": "\"1a29c592b09ee7725415efa354907426\""
}

2. Meine createDocInNuxeo Lambda-Funktion wurde automatisch aufgerufen:

createDocInNuxeo Lambda-Funktion wurde automatisch aufgerufen

3. Und wir können das Dokument auf der Nuxeo Platform sehen:

Dokument auf der Nuxeo Platform gesehen

Die Hauptdatei lautet FooFighters.pdf und ich kann sie herunterladen, um zu schauen, dass es sich dabei wirklich um die von mir hochgeladene Datei handelt.

Das ist schon alles! Sie können den Quellcode hier finden (ein Plug-in enthält den Code der Operation sowie den Lambda-Funktionscode).