引き続き、「作ればわかる! Google App Engine for Java プログラミング」本にてPythonを修行中。
前回の記事ではトランザクションを実装していなかったので、Avatarクラスのadd_articleメソッド内でput()していたけれど、片方だけが更新される可能性があった。
そこで、今回はトランザクションを実装してみることにした。
■トランザクションをどう実装するか?
今のところ、Google App Engine for Pythonには以下のパターンありそう。
- parent指定して同じエンティティグループに入れ、google.appengine.ext.db パッケージのrun_in_transaction()を使う*1
- Version 1.6.5 より使えるようになった、google.appengine.ext.db パッケージのrun_in_transaction_options()を使う (parent指定不要、High Replication Datastore必須)*2
- run_in_transaction_options()のデコレータ@db.transactional を使う *3
- Version 1.6.4 より実装されつつある、Python NDBのTransaction*4
どれを使うべきかを判断することができないが、今回の場合は手軽な run_in_transaction_options() を使用することに。小規模アプリなのでパフォーマンスについては考えないでおく。
■開発サーバーでHigh Replication Datastoreをシミュレートするには?
デフォルトのままでは利用できないため、起動オプションにて指定する。
Google App Engine Launcherにて対象のアプリを選択した後、Edit→Application Settings...→Launch Settignsに、以下を入力。
--high_replication
参考(英文のみ):Google Developers - Google App Engine The Python Development Server Using the Datastore
■実際のコード
実装方法としては、トランザクション化したい部分を別メソッドに切り出して呼び出すだけ。
今回は「Avatar」クラスの「add_article」メソッドからトランザクション部分を切り出し、「run_xg_transaction」メソッドとした。
GitHub - thinkAmi / 9784798123028_GAE
良いテスト方法が思いつかなかったので、トランザクション使用/不使用でソースを変えたり強制的に例外を起こすようにした(ソース中に記載あり)。その結果、
ということを確認できた。
# 継承メソッド群 def add_article(self, blog, text, keywords, nextPostTime): helper = flickr.flickr_helper.FlickrHelper() helper.fetch(keywords) pageUrl = '' imageUrl = '' if helper.id != '': pageUrl = helper.get_page_url() imageUrl = helper.get_image_url() # run_in_transaction_optionsによるトランザクション params = {'blogId': blog.id, 'text': text, 'nextPostTime': nextPostTime, 'pageUrl': pageUrl, 'imageUrl': imageUrl } # ★以下のニ行をアンコメントすることで、トランザクションを使わなくなる★ #self.run_xg_transaction(params) #return xg_options = db.create_transaction_options(xg=True, retries=3) db.run_in_transaction_options(xg_options, self.run_xg_transaction, params) def run_xg_transaction(self, params): jstnow = gae_util.Utility.get_jst_now() blogId = params['blogId'] blog = blogs.Blog.get_by_key_name(str(params['blogId'])) # 親ブログの更新 nextPostTime = params['nextPostTime'] if nextPostTime >= 0: # 本では時間を加算していたが、時間短縮のため分を加算する blog.nextPostDate = jstnow + datetime.timedelta(minutes=nextPostTime) blog.articleCount += 1 else: blog.nextPostDate = datetime.datetime(9999,12,31,0,0,0) blog.put() # ★強制的に例外を起こすためには、以下の一行をアンコメント★ #raise ValueError("*-------Error--------*") # 記事の登録 article = blogs.Article(id = jstnow, blog = blog, postDate = jstnow, text = params['text'], pageUrl = params['pageUrl'], imageUrl = params['imageUrl'], ) article.put()
記載に誤りがあれば、ご指摘ください。