2016年3月28日月曜日

ECMAScript2015 (ES6) のまとめ / class, import, export

mainpicture

あと2回でES6に関しては終了です。次回はdockerかpythonによる機会学習あたりのネタを書こうかな。haskellでもいいな。IoTネタも少し書きたい。

前々回はES6に追加されたシンタックスシュガーや変数宣言の方法、前回はパラメータの渡し方や展開の新しい方法についてまとめました。

今回は、いよいよES6のメインかもしれないClassとモジュールについて取り上げます。

より詳細を確認したい方はECMAScript2015の公式を確認すると良いかと。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => )
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )
  • class
  • import, export(今日はここまで)
  • Promise
  • Set, Map

今回はES6におけるclassとそれらを含むmodule importやexportをまとめます。


class

ES5以前でも同様のclassのようなものを作ることはできました。しかし、イマイチ読みづらく書きづらい。prototypeの理解をしないと少しイメージがしずらかったり、スコープを限定して書くこともやりにくく野良化しやすいです。

実例を出した方が理解が早いですね。まずはES5 ver。

// ES5

var User = function(name, mail) {
  this.name = name;
  this.mail = mail;
};

User.prototype.getName = function() {
  return this.name
}

var user1 = new User('Test User1', 'test1@test');
var user2 = new User('Test User2', 'test2@test');
console.log(user1.getName()); // Test User1
console.log(user2.getName()); // Test User2

// 後から宣言も可能
User.prototype.getMail = function() {
  return this.mail
}

console.log(user1.getMail()); // test1@test
console.log(user2.getMail()); // test2@test

// overrideも出来る
user1.getName = function() {
  return "<<" + this.name + ">>";
}
console.log(user1.getName()); // <<Test User1>>
console.log(user2.getName()); // Test User2

このように割と自由度高くどこでも再定義が可能で、prototypeを使ってmethodを定義するという形になります。(prototypeに関しては他の記述に任せますが、すべてのFunctionObjectの雛形のようなもので、newで生成したinstanceのクラスのbaseになるものです。)

さて上記例をどうようにES6で書き換えると次のようになります。

// ES6

class User {
  constructor(name, mail) {
    this.name = name;
    this.mail = mail;
  }

  getName() {
    return this.name;
  }
}

var user1 = new User('Test User1', 'test1@test');
var user2 = new User('Test User2', 'test2@test');

console.log(user1.getName());
console.log(user2.getName());

// これももちろんできるが、クラスに書くべし
User.prototype.getMail = function() {
  return this.mail
}

console.log(user1.getMail());
console.log(user2.getMail());

user1.getName = function() {
  return "<<" + this.name + ">>";
}
console.log(user1.getName());
console.log(user2.getName());

これで他の言語と同様にclassを定義することができるようになりました。

とはいえそれでも自由度の高い言語であることは変わらず、ダッグタイピングも可能ですし、そこでどんな定義をしても変数を突っ込んでも動かそうと思えば動かすことができます。

個人的にはthisあたりも冗長であることや、javascriptのthisは少し混乱を招きやすいのでthisは書きたくないなと感じています。


import, export

さて、Classを使えばある程度まとまった形でプログラムを書くことができるようになります。しかし、javascriptは依存関係の解決がとても難しい言語でした。htmlのhead内とかに記載しているため、どこでどの関数、変数が定義されているのか、参照しているソースコードはどれなのかとても追いにくく、関数名のバッティング等にも気をつける必要がありました。

しかし、ES6には標準でmodule化、importやexportの仕組みが用意され、お互いの依存関係を容易に示すことが可能になりました。

nodejsではrequireとmodule.exportで記載されていますが、ほぼ同様のことができるようになっています。

さきほどのUserクラスの簡易版をmodule化し、他のコードから読み込む場合は以下のようになります。

// lib.js

'use strict';

class User {
  constructor(name, mail) {
    this.name = name;
    this.mail = mail;
  }
  getName() {
    return this.name;
  }
}

export default User;
// main.js
'use strict'

import User from './lib';

var u = new User('hogehoge', 'hogehoge@hoge');
console.log(u.getName());

このような形になります。

自作のlibを参照する際には./で始めると相対パスで依存関係を解決しにいきます。npmでインストールしたlibなどはimport _ from 'lodash'import async from 'async'などのようにそのままimportすれば利用可能になります。

nodeの場合はimport、exportの記法が多少異なる( requireとかになる)のでご注意ください。あくまでもES6の標準です。

これで依存関係の把握が容易になり、global汚染もかなり解決されるようになりました。


私はできるだけ短く、できるだけ簡単にソースが書けるように頑張っております。すっきり書けるととても面白いものです。

さて、次回はES6に関しては最後になります。取り上げるのはES6で組み込まれたpromiseとset, mapに関してです。promiseは少し独特な動きをするので慣れるまで大変かもしれませんが、結構便利なものです。読んでいただければと思います。

では良いインプットと良いプログラミングを。

2016年3月22日火曜日

ECMAScript2015 (ES6) のまとめ / default parameter, spred operator, rest parameter

mainpicture

前回に引き続き、ES6の変更点を見ていきます。

より詳細を確認したい方はECMAScript2015の公式を確認すると良いかと。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => )
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )(今日はここまで)
  • class
  • import, export
  • Promise
  • Set, Map

前回は新しいarrow functionや変数宣言の新しいスコープであるletなどを取り上げました。

今回は、関数へ渡す引数やパラメータ展開の話がメインになります。


default parameter

rubyやpythonに見られるデフォルト引数という仕様です。引数に値が渡されなかった場合にこちらの値を利用することになります。メソッドのオーバーロードを書かなくて済むという利点がありますが、実際にどんな引数が渡ってきているのかを把握しておかないと何故か動いてしまうというソースを書いてしまうことになりかねないので注意が必要です。

ES5で実現するためには関数内で引数を取得するargumentsと呼ばれるオブジェクトを利用したり、関数名を変えたりしていることかと思います。

// ES5
var checkarg = function(i1,i2) {
  console.log(i1) # 1
  console.log(i2) # 2
  console.log(arguments['2'] || 'def') # def
}
checkarg(1,2)

これを次のように書き換えることができるようになりました。

// ES5
var checkarg = function(i1,i2,i3='def') {
  console.log(i1) # 1
  console.log(i2) # 2
  console.log(i3) # def
}
checkarg(1,2)

Firefoxには実装済みのようですが、chromeとnodeでは動作が確認できませんでした。

このデフォルト引数は、スクリプト言語では割と有用だと個人的には思っています。確かにメソッド内部で値の存在判定を行い、デフォルト値っぽいのを使うことができるのですが、多少煩雑ですし何を意図しているのか分かり難い感じになってしまうため、これを使えるとソースコードが読みやすくなりますね。


spread operator

私がこの演算子に出会ったのはreact-nativeを使ってアプリを実装しているときでした。react-nativeのNavigatorSceneConfigsのtransitionで使われています。

var FadeToTheRight = {
  ...FadeToTheLeft,
  transformTranslate: {
    from: {x: 0, y: 0, z: 0},
    to: {x: Math.round(SCREEN_WIDTH * 0.3), y: 0, z: 0},
  },
  translateX: {
    from: 0,
    to: Math.round(SCREEN_WIDTH * 0.3),
  }
};

・v0.15だったので今は違うかもしれないですが。
・上の例はES7のproposalで、babelがpluginを用いて利用可能にしています。純粋なES6の実装では使うことができません。

...FaceToTheLeftというのが中にあるのが確認できます。FadeTransactionの基本的な設定のようなものが、FaceToTheLeftに定義されていて(遷移の最後のほうでopacityが減るなど)、FadeToTheRightで一部変更がある部分を定義していることになります。

もう少し簡単なspread operatorの例を挙げてみましょう。

// ES5
var array1 = [1,2,3]
console.log(array1);
var array2 = [4,5,6]
console.log(array2);

console.log(array1.concat(array2)); 

最後のarray1の連結部分が

// ES6
var array1 = [1,2,3]
console.log(array1); // [1,2,3]
var array2 = [4,5,6]
console.log(array2); // [4,5,6]

console.log([...array1, ...array2]) // [1,2,3,4,5,6]

と、こんな風に書くことができます。

かなり見通しよく書けるようになりました。割とこの辺りはlodashとかを利用して 書くことが多いのですが、標準仕様で使えるようになるとありがたいですね。


rest parameter

最後はrest parameterについてです。他の言語だと可変長引数で同様のことができるようになっていたりします。

むしろjavascript今までできなかったのかよ的な雰囲気すらあります。おそらくこれまではargmentsでどうにかしていたのでしょう。

こんな感じになります。

//ES6
function expose(a1, ...rest) {
  console.log(a1) // 1
  console.log(rest) // [2,3]
}
expose(1,2,3);

node5系で動作させる場合には、コマンドに「–harmony_rest_parameters」というオプションを入れる必要があります。
node --harmony_rest_parameters hogehoge.js

残りのパラメーターが配列で入ってくるのが楽ですね。長さを気にせず渡すことができます。
(あまり長すぎるのも可読性が落ちるので気をつけましょう。)


今回は引数に関して主にまとめてみました。個人的にはbabelで利用出来るspread operatorのhash版が結構使えるやつなのでオススメです。

特にreateを実装しているとstateを書き換える部分があるのですが、そこを読みやすくかけるので。

this.setState({
  ...this.state,
  field: newValue,
});

さて、次回からES6のクラスとモジュール周りをまとめていきます。

では良いインプットと良いプログラミングを。

2016年3月14日月曜日

ECMAScript2015 (ES6) のまとめ / let, template string, arrow function

mainpicture

これをお読みの方は、どんな言語を用いて開発を行っているでしょうか。

javascriptはひと昔前までは主にブラウザ上で動きをつけるために利用され、私の感覚からするとその頃はとても書きにくい理解しづらい言語でした。(自分のプログラムの能力のせいもありますがw)しかし、nodejsが流行り始めた5,6年くらい前くらいからjavascriptはかなり勢いを増し、現在でもその勢いは大きく落ちることなく、結果多くの方が利用するようになっているのではないかなと思います。

最近ではnodejsも多くのPaaSの上で動作することが可能となり、バックエンドがjavascriptで記述することができるようになったため、フロントエンドとバックエンドで使用する言語を同一にするという目的のためにjavascriptを選択するのも良い選択になりました。

さらに、フロントエンドの開発が進んでいくうちにAngularJSreactといった数々のフレームワークが生まれ、SinglePageApplicationという考え方が生まれました。これらのフレームワークは今でも積極的にアップデートが重ねられ、リッチなWebアプリケーションを構築する際に大きく役に立ちます。

しかし、通常のjavascriptを利用して開発を行うと、言語仕様のために記述がしにくい箇所がでたり、冗長になったり、ソースコード量が多くなってしまうことがありました。それらを補うためにcoffeescripttypescriptなどが開発されるようになりました。

しかし昨年ECMAScript2015が制定され、ChromeやSafariなどの多くのブラウザによる実装が進んでいるため、coffeescripttypescriptで行われていた多くの部分を標準仕様で解決することができるようになりました。一部実装していないブラウザに関してはトランスパイラと呼ばれるES6をES5に変換するBabelのようなものもあるため、ES6は積極的に利用すべきであると思います。

そこで今回はECMAScript2015を数回に分けて紹介しつつまとめていきます。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => ) (今日はここまで)
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )
  • class
  • import, export
  • Promise
  • Set, Map

さて、少しずつみていきましょう。


let

ES5では変数宣言の際には

// ES5
var s = "I'm string"
var i = 123

このような形で定義していましたが、スコープが大きくなってしまうという問題がありました。

// ES5
function m() {
  var s1 = "s1";
  console.log(s1); // s1
  if (true) {
    var s1 = "changed";
    console.log(s1); // changed
  }
  console.log(s1); // changed
}
m();

これをlet宣言を使うことによりスコープをレキシカルブロック({}で囲まれた範囲)に宣言することができるようになりました。

// ES6
function m() {
  let s1 = "s1";
  console.log(s1); // s1
  if (true) {
    let s1 = "changed";
    console.log(s1); // changed
  }
  console.log(s1); // s1 ←変わらない!
}
m();

これでかなりスコープを限定して記述することが可能になりました。

swiftだとletは再代入不可ですが、ES6のletは再代入が可能です。
ES6の定数はconstで宣言します。


template string

これは他の言語にも良くある形だと思います。文字列フォーマットとも呼ばれているかもしれません。(厳密には文字列フォーマットは数値・文字の判別や数値の桁数などができるので違うかもですが)

// ES6
let pc = "Mac";
console.log(`I'm using ${pc}.`); // I'm using Mac.

こんな形で文字表現がかなり読みやすくなりました。これまでは+で連結しまくっていたので非常に楽です。


arrow function

むしろ今までなくて良くやってこれたなという雰囲気すら感じますが、関数宣言のシンタックスシュガーといえば良いでしょうか。

他の言語だとjava8から実装されたため同様の記法がjava8から使うことができ、Swiftにも同じような構文があります。RubyのProcオブジェクトやブロックとも似ています。

javascriptという性質上サーバーサイドとの連携が多く、遅延が発生するためそういった箇所はcallbackの嵐となるため、それをシンプルに書けるようにするためにはこのarrow functionが欲しくなってきます。

さて、どういったものなのかというと

// ES6
let f = () => {
  console.log("f is called");
}
f(); # f is called

これですね。少しややこしいケースも書いてみましょう。

// ES6
function post(data, callback) {
  setTimeout(() => {
    console.log(`Handle Data: ${data}`);
    callback({isOK: true});
  }, 3000);
}

function postDataToServer(data, callback) {
  post(data, callback);
}

function webapi(data, callback) {
  postDataToServer(data, callback);
}

console.log("Before Post");
webapi("Post Data", (response) => {
  console.log(response.isOK ? "Succeed!!" : "Failed...");
});
console.log("After Post");

このような形で時間のかかる処理(この例だとpost)の処理結果をcallbackが引き受けて、それをarrow functionで定義しておくとソースコードは短くてすみます。

別に読みにくければfunctionで代入も可能です。

// ES5
webapi("Post Data", function(response) {
  console.log(response.isOK ? "Succeed!!" : "Failed...");
});

これでもやっていることは全く変わりません。

ライブラリとかだと、第一に引数にerrorを渡し戻すものも多く存在します。また、この例のようにメソッドが一つくらいであればいいのですが、successとfailで二つのfunctionを定義しなくてはならなくなると徐々にめんどくさくなってきたりします。


さて、いかがでしたでしょうか。

今もまだ昔のjavascriptの書き方をしているのであれば、これを機に少しずつES6の書き方に変えていくのが良いかと思います。
次回は関数へのパラメータ周りを紹介していきます。

では良いインプットと良いプログラミングを。

2016年3月8日火曜日

Go言語とGoogleAppEngineに触れる / TaskQueue, PushQueue, PullQueue

mainpicture

Webサービスの処理に時間のかかる部分がある場合、それをQueueに入れて別プロセスのWorkerに処理をさせたいという部分があります。

例えば、メールを送信する処理であったり、画像の変換だったりですね。

Go言語においては最初はGo-Routineを利用すれば、非同期に処理ができたり、responseを返した後に処理を継続するということができるかなと思っていたのですが、responseが終了した時点でContextが閉じられてしまうせいなのかうまく動作せず、このあたりの方法を探っていたところでした。

Google App Engineには標準でQueueとWorkerを実行するための仕組みが備わっています。今日はその辺りをまとめていきます。


TaskQueue, PushQueue, PullQueueの違い

私は最初この三つは異なるものだと思っていましたが、TaskQueueの種類としてPushQueuePullQueueがあるという説明が載っていました。ということでPushQueuePullQueueの違いを見ていきましょう。


PushQueue

PushQueueは以下のような仕組みを備えています。

  • ProcessingRateという処理速度を用いてTaskを処理する。
  • App Engineが処理容量に応じて自動的にスケールする。
  • 処理が終わったら自動的にqueueを削除する。

特徴的なところは、

色々な値をデフォルトで設定し(queue.yamlで設定可能)App Engineが自動的に実行し掃除までしてくれる

というところです。

ちなみにApp Engine外のサービスから触ることはできません。


PullQueue

PullQueueは以下のような仕組みを備えています。

  • アプリケーション外のコードや他のアプリケーションがTaskを処理することが可能。
  • 処理時間やタイムフレームを明確に決めることができる。
  • 処理のボリュームにより自分でスケールさせたり、Queueの削除をする必要がある。

一般的なキューイングのシステムはこちらのイメージが近いです。単純なStackのイメージになります。

PushQueueと違い自動で実行はされません。


Go言語でPushQueueを使ってみる

現在開発しているサービスにてPushQueueを使う部分があったので実装例と詰まったところとかを記載していきます。

今回はApp Engineのdelayパッケージを利用しました。

サンプルコード

package main

import (
    "golang.org/x/net/context"
    "google.golang.org/appengine"
    "google.golang.org/appengine/datastore"
    "google.golang.org/appengine/delay"
    "net/http"
)

func init() {
    http.HandleFunc("/", main)
}

var delayPut = delay.Func("delayPut", func(c context.Context, data *Data) {
    keys := Put(c, validData)
})

var Put = func(c context.Context, data *data) []datastore.Key {
    // PutEntity
    keys, err := data.put(c)
    return keys
}

func main(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    delayPut.Call(c, data)
}

こんな感じになりました。

実装上の注意

さて上のサンプルでも利用しているdelayパッケージに関してですが、少し注意点があります。

  • FuncはTop-Levelのコンテキストで呼ぶこと。
    もし仮に、上記mainの関数内でdelayPutを定義したりすると、Task実行時にこのファイルがロードされても、mainの中身までロードする訳ではないため、delayPutが見つからず失敗するためだと思われます。

  • Funcの第二引数の返り値に意味があること。
    Funcの第二引数funcの返り値は複数返すことができますが、最後の値がerror typeであり、かつnilではない場合はもう一度そのメソッドを呼ぶという意味になります。

    実は、PushQueueは失敗したら設定した回数だけリトライされます。

  • テストが少し実装しにくいこと。
    delayFuncを呼んでいるmain()のテストが予想通りになりませんでした。time.Sleepしても、StronglyConsistentDatastore: trueにしても、datastoreへの更新がうまく動かなかったためです。そのため、Put()のように別なメソッドへ切り出して、その部分に対するテストを実装しています。

  • default queueにしかqueueを入れられないこと。
    調べたのですが出てきませんでした。メソッド内に特別入れられるような箇所もなく、delayパッケージではdefault queueにしか積むことが出来ないのかもしれません。おそらくTask Queue APIを利用すれば出来るかと思いますが、今回はそこまで触れませんでした。


queue.yaml

PushQueueの設定をするときは、queue.yamlを作成します。(以下はdefault queueに対する設定を行っています。)

細かい設定内容や詳細はGo Task Queue Configurationに載っています。

queue:
- name: default
  rate: 500/s
  bucket_size: 100
  max_concurrent_requests: 1000

今回はリトライに関するパラメータを一切設定していません。

設定を反映するときは

appcfg.py update_queues myapp/

を実行します。

rate, bucket_size, max_concurrent_requests

設定内容でとても分かりにくかった項目があります。少し私なりの補足を書いていきます。

bucket_size
これから理解するのが一番早いです。bucketとはバケツの意味であり、一度に運べる量みたいなものだと思ってください。(リボルバーの銃弾の装填数の最大数もイメージとして近いです。)
そのため、bucket_size=5のところにqueueが8個きた場合には、5個はすぐに処理用のマシン(プロセッサ)へ渡されますが、残りの3個に関してはbucketに空きが出るまで処理されません。(弾を5発詰めて発射します。残りは3個ですね。)
この仕組みは大量データ(大量queue)に対して、処理量を一様にするためにこのようになっています。もし仮にこの仕組みを採用しないとすると、処理をするマシン(プロセッサ)の数を都度増減させるか、データが最も来るときに合わせてそれを処理できるくらいの数に設定しておかなくてはいけません。
rate
bucket_sizeの空きをどれくらいの頻度・個数空けるかという設定値になります。書き方としては、5/s、10/m、3/hなど個数/[s,m,h]を指定できます。(上のリボルバーでいうと装填速度みたいなものです。)どういった設定値なのかというと、例えばbucket_size=5のところにqueueが8個到達するという場合を考えてみましょう。

rate=1/sの場合 1. bucket = 5, bucket空き = 0, 残りqueue = 3 2. 1s後、bucket = 0, bucket空き = 1, 残りqueue = 3 3. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 2 4. 1s後、bucket = 0, bucket空き = 1, 残りqueue = 2 5. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 1 6. 1s後、bucket = 0, bucket空き = 1, 残りqueue = 1 7. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 0

rate=2/sの場合 1. bucket = 5, bucket空き = 0, 残りqueue = 3 2. 0.5s後、bucket = 0, bucket空き = 1, 残りqueue = 3 3. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 2 4. 0.5s後、bucket = 0, bucket空き = 1, 残りqueue = 2 5. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 1 6. 0.5s後、bucket = 0, bucket空き = 1, 残りqueue = 1 7. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 0

このような流れとなります。bucketの回復速度みたいな感じですね。

公式の説明のキャプチャが分かりやすかったので貼っておきます。
enter image description here
max_concurrent_requests
単純に並列処理をする最大数です。datastoreがたくさんのrequestを受けて競合するのを避けるために使われます。
queue:
- name: optimize-queue
  rate: 20/s
  bucket_size: 40
  max_concurrent_requests: 10

この場合は並列で10ずつしか処理しないので、0.5sに10処理が完了すればqueueが詰まることはありませんね。かつ、並列処理を10に抑えているのでdatastoreの競合が起きにくいです。(多分それくらいなら発生しないかと)


さて、PushQueue, PullQueueに関しては以上です。
PullQueueを利用する機会があれば、そちらの方に突っ込んだ内容を書いてみようかと思います。

では良いインプットと良いプログラミングを。

2016年3月1日火曜日

Go言語とGoogleAppEngineに触れる / GoogleAppEngine

mainpicture

前回の記事でGo言語の第一印象とチュートリアルに関する気づきをまとめました。

今回はそれに引き続き、Google App Engineに関する気づきや詰まった点をまとめていきます。


Google App Engineに触れる

現在Google App Engineでは、java、python、PHP、Goをサポートしていますが、スピンアップが最も高速に立ち上がるのがGo言語のようです。

そのため、多くの方がGo言語Google App Engineへの利用を前提として使っているのではないでしょうか。

インストールや実行に関してはGoogle App Engine SDKなどを参照していただくことにして、実際に実装してきた中で問題となった箇所やその解決策を記載していきます。


json parse

webサービスの要件にrequest bodyにjsonがくるのでそれをparseして値を適宜処理するというものがありました。

他の言語やプラットフォームだと、body parserやjson parserがあり思っていた通りの動作をしてくれることが多いのですが、Google App Engineにはbody(io.Reader interfaceを持つ)を"encoding/json"json.NewDecoderへ渡し、structへ変換するという流れを取ります。

サンプルコードは以下のような形になります。

import (
    "encoding/json"
    "io"
)

type Data struct {
    Type     int      `json:"type"`
    Value    string   `json:"value"`
}

func ParseJson(r io.Reader) (*Data, error) {
    decoder := json.NewDecoder(r)
    data := Data{Type: -1}
    for {
        if err := decoder.Decode(&data); err == io.EOF {
            break
        } else if err != nil {
            return nil, err
        }
    }
    return data, nil
}

こんな感じになります。

struct`json:"type"`というようなtagをつけることにより、jsonのnameをstruct内のfieldにマッピングします。データの方はstructと一致している必要があります。

問題1. 文字列と数値

jsonではvalueに数値がある場合には”“で囲まないようです。そのため、”“が入力値となっている値をintにマッピングしようとするとエラーを出力します。(個人的にはzero valueという仕組みを持っているのだから0入れておいてよと思いますが。)

これはクライアントアプリとの仕様の問題なので、今回は全てstringで値をもらい、後から全て変換する方針にしました。

問題2. マッピングが失敗する

これの解決が一番時間がかかりました。ある特定のfieldだけ値のマッピングがされないという事象に遭遇したのです。

よく見てみるとtagの中にスペースが入っていることが分かり、これを消せばマッピングがうまく動いてくれました。

json: "type"json:"type"です。


カスタムヘッダー

今回オリジナルのヘッダーをクライアントが送信してくるので、サーバーがそれを取得して値を使うという実装がありました。実装自体はそんなに難しくなく、SDKもきちんとHeaderを取得するためのインターフェースを備えていました。

ソースコードしては以下のような感じです。

const MY_HEADER = "x-my-header"

func GetHeader(r *http.Request) string {
    h := r.Header[MY_HEADER]
    if h == nil {
        return ""
    }
    if len(h) == 0 {
        return ""
    }
    return h[0]
}

問題. カスタムヘッダーが取得できない

実際にテストコードは動き、ローカルサーバーも無事に動作することを確認しました。問題は本番環境にデプロイした後に発生しました。

Headerの取得ができない!!

Headerの内容をログに書き出してみると、Google App Engine内ではHeader Keyがキャメルケースになるようです。(RFCを読んでいないので詳細はわからないのですが)

そのため上記サンプルのx-my-headerX-My-Headerに修正する必要があります。

気をつけましょう。


datastore

問題. datastore console viewが正常に動作しない

ある構成のstructをdatastoreに入れたところ、console viewからデータが確認できなくなってしまいまいした。

これに関しては現在調査中で、実際に値が入っているかどうかはwebサービスにendpointを一つ用意して、そのendpointロジック内でqueryを発行して結果を出力して確認できるようにしましたが、いまいちです。

情報. Ancestor(祖先)は自動生成

datastoreの概念にancestorというentityのグルーピングや親子関係を表現するための仕組みがあります。ソースコードではdatastore.NewKey(c, "datastorename", "", 0, ancestor) のようにkeyを生成する際に第5引数に入れます。

このancestor keyに関しては、もし存在しない場合には自動で生成されるので、先に作っておいたりする必要はありません。


interface{}型

Go言語でのinterfaceの定義の仕方は

type Interfacer interface {
    Method() string
}
func (d *Data) Method() string {
    return "hogehoge"
}

このような形でした。interfaceとはメソッド群を定義しています。言語仕様を読むと、全ての型はinterface{}という空のinterfaceを実装しているとの記載があります。

このinterface{}はjavaでいうObject型のような扱いをすることができ、かなり抽象度を上げた取り扱いをすることができます。
また、以下のような形でinterface型から値を取り出し、 それぞれの型に適した処理を実装することができます。

switch i := x.(type) { // xがinterface{}型
case nil:
    printString("x is nil")
case int:
    printInt(i)  // iはint
case float64:
    printFloat64(i)  // iはfloat64
case func(int) float64:
    printFunction(i)  // iは関数
case bool, string:
    printString("type is bool or string")
default:
    printString("don't know the type")
}

testing

前回の記事でも簡単に取り上げましたが、datastoreのテストをする際に一貫性というキーワードが大切になります。これはdataの性質を表すACIDの一つであり、処理が正しく行われた場合は、その処理は一貫した結果を返すという類のものです。

なので誰かがデータを更新した直後に他の人がデータを読みにいったケースを考えると、一貫性が保たれているシステムの場合は更新したデータを100%読むことができます。

例えば金融機関、人事給与の給与データなどはそういう設計をすべきです。

一貫性が保たれていないシステムの場合は、書き込みの直後に読み取りにいってもデータが変わっていない可能性があります。

例えば、ログデータであったり、チャット内容であったり、DNSレコードの更新であったりは、書き込みしたという事実があれば、読み取りは直後でなくてもよいという性質があります。

さて、Google Cloud Platformのdatastoreは一貫性を保証していません。(厳密言えばqueryにancestorを指定した場合など、強い一貫性を持たせて実行することができる箇所もあります)そのためテストを行う際に問題が生じます。それはテストロジックの最初の方でデータをinputするロジックを追加したのに、テストメソッドの箇所では反映されておらずまともにテストができないということです。

今回これにはまりました。

解決策はaetest.NewInstanceを生成する際にオプションでStronglyConsistentDatastore: trueを追加する必要があります。

確かにこのオプション名は強い一貫性のdatastoreにするという意味になりそうですね。

なのでContextを生成する部分を以下のように書き換えます。

opt := aetest.Options{
    AppID: "test",
    StronglyConsistentDatastore: true,
}
inst, _ := aetest.NewInstance(opt)
defer inst.Close()
req, _ := inst.NewRequest("POST", "/", nil)
c := appengine.NewContext(req)

このcを全てに使い回せば、一貫性を保ったテストを記載することができます。


いかがでしたでしょうか。個人的にはGo言語自体は嫌いではないです。ただし、自由度がかなり制限されていると感じています。誰が書いてもしばらくは割と似たようなソースコードになるのではないでしょうか。

また、rubyやnodeに比べると周囲のライブラリがまだ貧弱でやることは少し多くなるのかもしれないなと思います。

ただ、Google App Enginge上での速度は捨てがたいですけどね。

今日はここまでです。

では良いインプットと良いプログラミングを。