去年の12月、私は、幸運にもreInventに再び参加し、素晴らしい時を過ごしました。5日間にわたって興味の尽きない会話、セッション、ゲームやパーティもありました(ラスベガスですから、当然!)。イベントで一番ホットだった話題を選ぶのは難しいのですが、機械学習、IoT、ビッグデータ、コンテナ、セキュリティ、データベース、サーバレスなど、発見も学ぶべきこともたくさんありました。

昨年AWS Lambdaに愛着をもってから、このテーマについての実践的なラボにはいつも参加しています。Lambdaが役立つアプリケーションの範囲は無限ですが、私が参加したセッションでは、古典的テーマの一つ、画像がAWS S3バケットに追加されたときにNuxeoでサムネイルを自動的に作成するについてでした。

私がこれを「古典的テーマ」と呼ぶのは、これが公式ドキュメントに例として掲載されており、そのソースAmazon Web Services - Labsのgithubにあるからです(このラボをご存じない場合は、必ずご覧になってください!)。

このテーマはクールなアイデアを与えてくれました。LambdaはImageMagickを使って単純な画像処理操作を行う方法を知っているので、これをNuxeo Content Services Platformと統合して、すべてのサムネイルをLambdaを使って生成することができます

##使用事例

S3に保存されているすべてのバイナリを使用してNuxeoデジタルアセット管理アプリケーションを実行しているとしましょう。その場合、新しい画像をアップロードするたびに画像処理を行う必要があります。ブロブはすでにS3に入っているので、この処理をラムダ関数に委託し、結果を同じバケットに戻したいとします。

##設計

サムネイル生成の例を取り上げますが、ニーズに合わせて簡単に変更できます。基本的には、次のプロセスが必要になります: Nuxeo Platformが画像をバケットに格納するたびに、ラムダ関数を呼び出してサムネイルを生成し、同じバケットに戻してから、その情報でNuxeo Platformの既存のドキュメントを更新します。もちろん、サムネイルはすでにバケットに入っているので、再アップロードする必要はなく、単にサムネイルをポイントするだけです。

上記には明らかな問題があります。Lambda関数がs3:ObjectCreated:Putによって呼び出され、サムネイルが同じバケットに追加された場合、サムネイルの生成を無限ループさせないようにするにはどうすればよいでしょうか?

ここに解決策があります。Amazon S3は、オブジェクトをグループ化するためにバケット内にフォルダを持つことをサポートしており、Lambda関数は特定のフォルダのイベントによってトリガされるように設定できます。したがって、無限ループを回避するために、バケットには2つのフォルダがあります。1つは元のイメージを格納し、もう1つは生成されたサムネイルを格納します。もちろん、Nuxeo Platformが確実に、生成されたサムネイルにアクセスするようにしなければなりません。

Nuxeo Platformは、キー(名前)としてダイジェストを使用してS3にバイナリを格納し、バケットの特定のフォルダを使用するように設定できます(プロパティnuxeo.s3storage.bucket_prefixを使用します)。この状態で、生成されたサムネイルをNuxeo Platformで使用されるフォルダに移動する必要があります。それを/nuxeoと呼びましょう。

作成する必要があるもの:

1.サムネイルを新しいフォルダ(/thumbnails)に生成し、新しいオブジェクトが/nuxeoフォルダに追加されるたびに呼び出されるLambda関数。この関数は、「s3:ObjectCreated:Put」イベントで呼び出されます。

2.新しいファイルを/thumbnailsフォルダから/nuxeoに移動するLambda関数。移動が成功すると、Nuxeo Platformの操作を呼び出し、新しく生成されたサムネイルの情報を元のドキュメントに対して更新します。コピーによるオブジェクトの作成は「s3:ObjectCreated:Copy」イベントをトリガするので、サムネイルからサムネイルを再生成するように第1の関数に通知することはありません。

バケットの構造バケットの構造

シーケンス図シーケンス図

##実装 この事例のソースコード(Lambda関数とNuxeo演算の両方を含んでいる)
は、githubにあります。私は構成部分については説明しませんが、Lambda関数を実行するユーザには、バケットからの読み書きに必要な権限を与える必要があることに注意してください。そのためには、Nuxeo Platformがバケットにアクセスするために必要なセキュリティポリシーと同じものを添付することができます(AWS設定文書に説明)。

ここで実装を分析してみましょう(私のNuxeoサーバが、バイナリをバケットtest-picture-views-with-lambda、フォルダ/nuxeoに保存するように既に設定されていると仮定しています)。

1.まず、Nuxeo Platformで最初のサムネイル生成を無効化する必要があります

これは、最初にサムネイルを生成するリスナーを無効にするための簡単な投稿によって実行できます。

  <listener name="updateThumbListener" enabled="false"/> 
  <listener name="checkBlobUpdate" enabled="false" />

2.generateThumbnails Lambda関数を作成します

青写真の「画像処理サービス」を選択することで、最初からLambda機能を作成することもできますし、generateThumbnailsからコードをアップロードすることもできます(ImageMagick関連のライブラリを使用していますので、フォルダをzipして既存のノードモジュールをすべてアップロードすることを忘れないでください)。新しいオブジェクトがNuxeo Platformによって作成されたときに呼び出されるように設定します。

generateThumbnails

これが関数の興味深い部分です。

  var digest = crypto.createHash('md5').update(data).digest('hex');
  var dstKey = 'thumbnails/' + digest;
  s3.putObject({
      Bucket: dstBucket,
      Key: dstKey,
      Body: data,
      ContentType: contentType,
      Metadata: {
          originalFileDigest:originalFileDigest
      }
  },
  next);

これは、ダイジェストをキーとして使用して新しい生成されたサムネイルを保存し、元の画像のダイジェストを保存するので、ドキュメントをNuxeo Platformで見つけて、サムネイル情報を更新することができます。

3.moveThumbnails Lambda関数を作成します

moveThumbnails.jsのコードを更新します。この機能は、サムネイルを/nuxeoフォルダに移動(コピー)した後、Nuxeo Platformで操作を呼び出して文書の情報を更新します。

moveThumbnails

   var thumbnailDigest = srcKey.substring('thumbnails'.length + 1, srcKey.length);
  var newKey = 'nuxeo/' + thumbnailDigest;

  //Copy - Pasting the object won't trigger a 'putObject' event
  s3.copyObject({
      Bucket: dstBucket,
      Key: newKey,
      CopySource: srcBucket + '/' + srcKey
  }, (err, data) => {
      if (err) {
          console.log('Error copying file:' + err);
      } else {
          //get the object to read the original file digest stored in metadata
          s3.getObject({
                  Bucket: dstBucket,
                  Key: newKey
          }, function(err, data) {
                  if (err) console.log(err, err.stack);
                    else
                  updateThumbnails(data.Metadata['originalfiledigest'], thumbnailDigest);
                   });
             }
  });

4.Nuxeo PlatformでSetThumbnail.java演算を作成します

nuxeo-thumbnails-with-lambda

jarファイルを構築し、それをサーバに展開することができます。これには、Nuxeo Platformで元のサムネイル生成を無効にするための操作と投稿が含まれています。S3バイナリマネージャは、バケット内のオブジェクトのキーとしてブロブのダイジェストを使用することを期待しているので、(Nuxeo Platform内のドキュメントを見つけるために)オリジナル画像のダイジェストとサムネイルのダイジェストを渡す必要があります。もちろん、ファイルが既にバケットに入っているので、ブロブを再アップロードすることはありません。このダイジェストを使用して新しいブロブを作成し、それをサムネイルとして設定するようNuxeo Platformに指示します。

上のコードからわかるように、元の画像のダイジェストを生成されたサムネイルのS3カスタムメタデータとして設定しているので、すでに両方を持っています。

メタデータ

  DocumentModelList docs = session.query(String.format("Select * from Document where content/data = '%s' ",originalFileDigest))
      for (DocumentModel doc : docs) {
          doc.addFacet("Thumbnail");
          BinaryBlob sb = new BinaryBlob(new LazyBinary(thumbnailDigest, "default", getCachingBinaryManager()),
                  thumbnailDigest, (String) doc.getPropertyValue("file:content/name"), "image/png", null,
                  thumbnailDigest, -1);
          doc.setPropertyValue("thumb:thumbnail", sb);
          session.saveDocument(doc);
            }

これで終わりです。それでは、実際の手順を見てみましょう。

##実際に使ってみる!

Nuxeo Platformに画像をアップロードするだけです。

Nuxeoで画像をアップロード

自動的にサムネイルが表示されます。

サムネイル

ここで私のラムダ関数がすべての仕事をしたという証拠があります(もちろん、私を信じてくださっているとしても、ご自分で試してください!):

Lambdaによって生成されたサムネイル

Lambdaによって移動されたサムネイル