Riak doc の Commit Hooks を意訳してみた


# Commit Hooks

大雑把な意訳、訳しにくい箇所は英文のまま転載します。
http://docs.basho.com/riak/latest/references/appendices/concepts/Commit-Hooks/

## Overview

Pre-commit Hook と Post-commit Hook は、Riak Object がコミットされる前あるいは後に呼び出されます。これによりあらゆるアプリケーションの機能性を大きく高めることができます。Commit Hooks は以下のことができます。

* allow a write to occur with an unmodified object　
* modify the object　
* Fail the update and prevent any modifications　

Post-commit Hock はコミット後に通知され、Riak Object を修正するべきではありません。Riak Object 更新時の Post-commit Hock は、不快な feedback ループを引き起こす場合があります。そのようなサイクルを検知し短路させるよう注意深く書かなければ、不快な無限ループが発生します。

Pre-commit Hook と Post-commit Hook は、各 bucket 単位で定義され、対象 bucket のプロパティに格納されます。彼らはクライアントへの成功レスポンス毎に一度動作します。

## Configuration

Pre-commit Hook と Post-commit Hook の設定は非常に簡単です。単に、あなたの hook 関数のリファレンスを bucket プロパティの関数が格納されるリストに追加してください。Pre-commit hooks は bucket プロパティの "precommit" に格納されます。Post-commit hooks は bucket プロパティの "postcommit" に格納されます。

Pre-commit Hook は Javascript 関数あるいは Erlang 関数の名前付き実装ができます。各々のための設定は次のように与えられます。

```
Javascript: {"name": "Foo.beforeWrite"}
Erlang: {"mod": "foo", "fun": "beforeWrite"}
```

Post-commit Hook は Erlang のみで実装できます。詳細は [Erlang Named Functions](http://docs.basho.com/riak/latest/cookbooks/Erlang-Named-Functions/) を参照してください。この制約は JavaScript が Erlang のコードを呼ぶことができないため、あらゆる有用な処理が妨げられるからです。This restriction will be revisited when the state of Erlang/Javascript integration is improved. Post-commit Hook は Pre-commit Hook と同じ function reference syntax を使用します。See [MapReduce Implementation](http://docs.basho.com/riak/latest/references/appendices/MapReduce-Implementation/) for steps to define your own pre-defined Javascript named functions.

## Pre-Commit Hooks

### API & Behavior

Pre-commit Hook 関数は、ひとつの引数 (修正済みの Riak Object) をとるべきです。削除は書き込みと思われることを思い出してください。したがって、削除が発生したときに Pre-Commit Hook が動作するでしょう。Hook 関数は、いつ削除が起こっているかを決めるために、Object に X-Riak-Deleted メタデータが含まれているかを調べる必要があります。

Erlang の Pre-Commit Hook 関数の返り値は、次の 3 つのケースがあります。

* **Riak Object:** 関数に渡された同じ Object か、更新したバージョンを返します。これは Hook が Riak に書き込まれる前の Object を編集することを可能にします。

* **fail:** The atom fail will cause Riak to fail the write and send a 403 Forbidden along with a generic error message about why the write was blocked.

* **{fail, Reason}:** The tuple {fail, Reason} will cause the same behavior as in #2 with the addition of Reason used as the error text.

Erlang Pre-Commit Hook 関数の処理中に発生したエラーは "sasl-error.log" ファイルにエラーが発生した行番号とエラーレポートが出力されます。

Erlang Pre-commit Example:

```
%% Limits object values to 5MB or smaller
precommit_limit_size(Object) ->
  case erlang:byte_size(riak_object:get_value(Object)) of
    Size when Size > 5242880 -> {fail, "Object is larger than 5MB."};
    _ -> Object
  end.
```

Javascript Pre-commit Hook 関数は、ひとつの引数 (修正済みの Riak Object の JSON エンコード) をとるべきです。JSON フォーマットは Riak の map/reduce と正確に同じです。Javascript Pre-Commit Hook 関数の返り値は、次の 3 つのケースがあります。

* **A JSON encoded Riak object:** JSON を使用することは別として、Erlang 関数と完全に同じです。Riak は書き込む前に Native フォーマットに自動的に変換します。

* **fail:** The Javascript string “fail” will cause Riak to fail the write in exactly the same way as #2 for Erlang functions.

* **{"fail": Reason}:** The JSON hash will have the same effect as #3 for Erlang functions. Reason must be a Javascript string.

Javascript Pre-commit Example:

```
// Makes sure the object has JSON contents
function precommitMustBeJSON(object){
  try {
    Riak.mapValuesJson(object);
    return object;
  } catch(e) {
    return {"fail":"Object is not JSON"};
  }
}
```

### Chaining

bucket precommit プロパティのデフォルト値は空のリストです。Adding one or more pre-commit hook functions, as documented above, to the list will cause Riak to start evaluating those hook functions when bucket entries are created, updated, or deleted. Riak stops evaluating pre-commit hooks when a hook function fails the commit.

### Pre-commit Validation Example

Pre-commit Hook は Riak で様々な方法で使用することができます。ひとつの方法として、Riak に書き込む前のデータの validate するために Pre-commit Hook は使用されます。Riak に書かれる前の JSON Object を validate するために Javascript を使用する例を次に記載します。

```
//Sample Object
{
  "user_info": {
    "name": "Mark Phillips",
    "age": "25",
  },
  "session_info": {
    "id": 3254425,
    "items": [29, 37, 34]
  }
}


var PreCommit = {
    validate: function(obj){

      // A delete is a type of put in Riak so check and see what this
      // operation is doing

      if (obj.values[[0]][['metadata']][['X-Riak-Deleted']]){
        return obj;
      }

      // Make sure the data is valid JSON
      try{
         data = JSON.parse(obj.values[[0]].data);
         validateData(data);

      }catch(error){
        return {"fail": "Invalid Object: "+error}
      }
      return obj;
    }

};

function validateData(data){
  // Validates that user_info object is in the data
  // and that name and age aren't empty, finally
  // the session_info items array is checked and validated as
  // being populated

  if(
      data.user_info != null &&
      data.user_info.name != null &&
      data.user_info.age != null &&
      data.session_info.items.length > 0
  ){
    return true;
  }else{
    throw( "Invalid data" );
  }
}
```

## Post-Commit Hooks

### API & Behavior

Post-commit Hook 関数は書き込みが成功した後に動作します。具体的には、呼び出したプロセスが書き込みが成功した通知を受ける直前に、riak_kv_put_fsm によって Hook 関数が呼ばれます。Hook 関数はひとつの引数 (書き込まれたばかりの Riak Object インスタンス) を必ず取ります。Hook 関数の返り値は無視されます。

Pre-commit Hook と同様に、削除は書き込みと思われることを思い出してください。したがって、削除が発生したときに Post-Commit Hook が動作するでしょう。Hook 関数は、いつ削除が起こっているかを決めるために、Object に X-Riak-Deleted メタデータが含まれているかを調べる必要があります。Post-Commit Hook 関数の処理中に発生したエラーは "sasl-error.log" ファイルにエラーが発生した行番号とエラーレポートが出力されます。

Post-commit Example (Erlang):

```
%% Creates a naive secondary index on the email field of a JSON object
postcommit_index_on_email(Object) ->
    %% Determine the target bucket name
    Bucket = erlang:iolist_to_binary([riak_object:bucket(Object),"_by_email"]),
    %% Decode the JSON body of the object
    {struct, Properties} = mochijson2:decode(riak_object:get_value(Object)),
    %% Extract the email field
    {<<"email">>,Key} = lists:keyfind(<<"email">>,1,Properties),
    %% Create a new object for the target bucket
    %% NOTE: This doesn't handle the case where the
    %%       index object already exists!
    IndexObj = riak_object:new(Bucket, Key,<<>>, %% no object contents
                               dict:from_list(
                                 [
                                  {<<"content-type">>, "text/plain"},
                                  {<<"Links">>,
                                   [
                                    {{riak_object:bucket(Object), riak_object:key(Object)},<<"indexed">>}]}
                                 ])),
    %% Get a riak client
    {ok, C} = riak:local_client(),
    %% Store the object
    C:put(IndexObj).
```

### Chaining

bucket postcommit プロパティのデフォルト値は空のリストです。Adding one or more post-commit hook functions, as documented above, to the list will cause Riak to start evaluating those hook functions immediately after data has been created, updated, or deleted. Each post-commit hook function runs in a separate process so it's possible for several hook functions, triggered by the same update, to execute in parallel. All post-commit hook functions are executed for each create, update, or delete.

## おわりに

手抜きでゴメン。