AttsuBlog

ATTSU INC. Official Blog
IMGP3438_Fotor
0 comments

ArangoDBとClojure その3

タグ:,

こんにちは、萬屋です。

春は散歩が気持ち良い季節ですね。写真は家の近くを散歩していた時に見つけた猫です(*´ω`*)
ちなみに自分は猫派です(聞いていない)
人間と猫の付き合いは長く、世界最古の飼育例は約9500年前のキプロスから見つかっているそうです。
キプロスは地中海に浮かぶ小さな島国です。キプロスの料理でハルーミ(ハロウミ)というヤギのミルクから作ったチーズがありますがこれが絶品なので、是非機会があればお試しください(・∀・)

なんか豆知識系の前振りが定着してきましたねw

本題に移りましょう(^_^;)

ArangoDBとClojureシリーズ(?)第三弾、今回はトランザクションです。

NoSQLだとトランザクションがないイメージが何となくありますが、ArangoDBはできます!

商品コレクションと商品画像コレクションがあって、商品に対して画像が複数登録できるようなケースを考えてみます。
以下、サンプルです。


(ns arango-test.core
  (:gen-class)
  (:require [travesedo.collection :as col]
            [travesedo.database :as db]
            [travesedo.document :as doc]
            [travesedo.query :as q]
            [travesedo.transaction :as transaction]))

;; 作成するデータベース名
(def db-name "clojure_test")
;; ArangoDBのホスト名
(def arango-host "http://127.0.0.1:8529/")
;; DBユーザ名
(def db-user "user")
;; DBパスワード
(def db-password "password")

;; データベースの作成
(defn create-database
  []
  (db/create {:conn {:type :simple
                     :url arango-host}
              :payload {:name db-name
                        :users [{:username db-user
                                 :password db-password}]
                        }}))

;; ArangoDB接続設定)
(def CTX {:conn {:type :simple
                 :url arango-host
                 :uname db-user
                 :password db-password}
          :db db-name})

;; ArangoDB操作時のパラメータを作成
(defn create-context
  "Create Context"
  [ctx]
  (conj ctx CTX))

;; コレクションの作成
(defn create-collection
  [name]
  (col/create (create-context {:payload {:name name}})))

;; 商品と画像の登録
(defn add-item
  [name price images]
  (let [action "function(params) {
                  var db = require('internal').db;
                  var item = db.item.save({name: params['name'], price: parseInt(params['price'])});
                  for (var i = 0, l = params['images'].length; i < l; ++i) {
                     var image = db.item_image.save({image: params['images'][i], item_key: item['_key']});
                     if (image['error'] == true) {
                       throw 'Item Image Failed!!';
                     }
                   }
                   return item['_key'];
                 }"
         params {:name name
                 :price price
                 :images images}
         ctx (create-context {:payload {:collections {:write ["item" "item_image"]}
                                        :action action
                                        :params params}})
         result (transaction/transact ctx)]
     (println result)))
 
 (defn -main
   "I don't do a whole lot ... yet."
   [& args]
   ; データベース作成
   (create-database)
   ; 商品コレクション作成
   (create-collection "item")
   ; 商品の画像コレクション作成
   (create-collection "item_image")
   ; 登録してみる
   (add-item "商品1" "200" ["image1" "image2" "image3"])
   ; 取得してみる
   (println (-> {:payload {:query "FOR i IN item 
                                   FILTER i.name == '商品1'
                                     RETURN {
                                       name: i.name, 
                                       price: i.price, 
                                       images: (
                                         FOR im IN item_image
                                           FILTER i._key == im.item_key
                                           RETURN im
                                       )
                                     }"}} create-context q/aql-query)))

add-item関数がトランザクションを行っている部分です。
actionの中身を見るとどこかで見たことがあるような……
そう、Javascriptですね(^o^)

最初にitemコレクションに登録をして、その結果のキーをitem_imageコレクションに入れていきます。
その途中でエラーが発生した場合はthrowで例外を投げると、それがrollbackになり、最後のreturnでcommitします。

ではlein runで実行です!
すると……


Exception in thread "main" java.lang.RuntimeException: EOF while reading, starting at line 10, compiling:(travesedo/transaction.clj:31:1)

ガ━━(;゚Д゚)━━ン!!

なん……だと……
なんとライブラリtravesedo側のエラーです……
ソースを見るとtransaction.cljの最後の綴じカッコがない……!
GitHub上では修正されているのですが……

仕方がないので直接travesedoのソースをGitHubから落としてきて、src/travesedoをまるごとプロジェクトのsrcに追加します。
更にtravesedoはclj-httpとorg.clojure/tools.traceが依存関係にあるので、project.cljのdependenciesに追加しましょう。


  :dependencies [[org.clojure/clojure "1.6.0"]
                 ;[deusdatsolutions/travesedo "0.1.3"] コメントアウトする
                 [clj-http "1.0.0"]
                 [org.clojure/tools.trace "0.7.8"]]

これでlein depsを実行した後、lein run再びです!
すると


{:code 200, :error false, :result 280243854874}
{:has-more false, :code 201, :error false, :extra {:stats {:writesExecuted 0, :writesIgnored 0, :scannedFull 4, :scannedIndex 0, :filtered 0}, :warnings []}, :result [{:name 商品1, :price 200, :images [{:image image2, :item_key 280243854874, :_id item_image/280244379162, :_rev 280244379162, :_key 280244379162} {:image image1, :item_key 280243854874, :_id item_image/280244248090, :_rev 280244248090, :_key 280244248090} {:image image3, :item_key 280243854874, :_id item_image/280244510234, :_rev 280244510234, :_key 280244510234}]}]}

できたー(^^)

では強制的にロールバックしてみましょう。
add-item関数を以下のように書き換えます。


(defn add-item
  [name price images]
  (let [action "function(params) {
                  var db = require('internal').db;
                  var item = db.item.save({name: params['name'], price: parseInt(params['price'])});
                  for (var i = 0, l = params['images'].length; i < l; ++i) {
                     var image = db.item_image.save({image: params['images'][i], item_key: item['_key']});
                     throw 'Item Image Failed!!';
                   }
                   return item['_key'];
                 }"
         params {:name name
                 :price price
                 :images images}
         ctx (create-context {:payload {:collections {:write ["item" "item_image"]}
                                        :action action
                                        :params params}})
         result (transaction/transact ctx)]
     (println result)))
 

for文の中のif文を抜いて、しょっぱなから例外を発生させます。
これで実行してみます。


{:errorMessage An error has occurred during execution: Item Image Failed!!, :errorNum 500, :code 500, :error true, :exception Item Image Failed!!}
{:has-more false, :code 201, :error false, :extra {:stats {:writesExecuted 0, :writesIgnored 0, :scannedFull 0, :scannedIndex 0, :filtered 0}, :warnings []}, :result []}

itemコレクション登録の結果で、:exceptionに投げた例外の結果が表示されていますね。
取得件数も0件です。ちゃんとロールバックされていますね(・ω<) 今回はこのへんで(^_^;) もう長くなってしまうのは必須なのか……orz 次回も続きます!


Leave A Comment