emahiro/b.log

Drastically Repeat Yourself !!!!

2018年 ~抱負~

エンジニアとしてコミットしたと言えるプロダクトをつくる

コミットしそこねたプロダクト、コミットしたくてもできなかったプロダクト、いままでのキャリアで「これをしてきた!作ってきた!」と言える仕事に巡り合ってこなかったので、今年こそはこの目標を達成したい。
まずは今仕事で関わっているサービスにコミットすること、エンジニアとして技術でコミットすることから逃げないこと。
そして、「ただ作ること」から「よく作ること」へ自分自身の価値観のスタンダードをあげていきたいと思っている。

月2冊以上の技術書を読む

技術書を読むことは引き続き継続していきたい。
書籍の種類や内容によっては時間がかかるものがあるかもしれないけれど、今年はとにかく量を読むことに挑戦したい。
書いてあること全てを覚えなくてもいいので、量をこなして、知識のindexを色々な所に張り巡らせる1年にして行きたいと思っている。

qiita.com

githubへの1commit/weekをしていくこと

テーマ、規模は問わず、週一以上はgithubに草生やすことを目標にしたい。これの延長で何かサービスを作れたらと思っている(けどそこまで自分に期待するのはやめておく。)

github.com

新しいサービスは積極的に使っていく。

引き続き新しいサービスは積極的に触っていきたい所存。
そろそろお金がかかるサービスであっても積極的に使っていくようにしたい。これについては service safari にはいつもお世話になっているので、今年もよろしくお願いします。

www.service-safari.com

新しい言語を一つ仕事レベルで習得すること

去年はGolangを習得したので、今年も何かを実用レベルで習得したい。
今の所チェックしているのはnode。あまり触れてこなかったけど、習得していると今後サーバーサイドだけでなく、フロントエンドや、BFFといった設計にも役立ちそうななので、今年はぜひ習得したいと思っている。

とりあえず思いつく限りではこんな感じ。 技術以外では

  • 自炊のレパートリーを増やすこと
  • ブログを引き続き継続して書くこと
  • 資産運用や税制といったファイナンシャルの教養を身につけること

このあたりをテーマにやっていきたいと思う。

今年もよろしくお願いしますmm

2017年 ~振り返り~

一年も終わりなので今年の振り返りをします。
振り返り観点は以下

  • スキルスタック
  • 個人的トピック

技術スタック (個人的達成度)

Skill Commit Comment
Rails 4からあげる時に1から色々触ってました
合わせてrubyも1からいろんな書籍、資料を読んで学んだけど、いまいちrailsのエコシステムそのものには馴染めないなーと思いました。
Swift iOSとはいえ、スマホアプリ開発を理解できるようになったことはポジティブに技術の幅が広がってよかった。
とはいえ、実践で使うにはまだ稚拙...。
swift自体は非常に開発しやすくていい言語
関数型っぽいパラダイムにはいまいち馴染めないなと感じました。
Golang 今年のメイントピックの一つ
部署を異動して、Goでの開発を始めましたが、シンプルな言語仕様に加えて、エコシステムが出来上がっているので非常に馴染みやすかったです。
web系と言われるLL言語を使ってた時に感じていた課題感を大体クリアすることができていて、web系の新しいスタンダードになると思う言語だと実感しました。
ライブラリを改めて読むくせがついたことが今年の最大の進歩だと思います。(これは開発環境をIDEに移行したことも大きく寄与していると思います。)
GAE Goと並んで今年覚えたトピックの一つ。
インフラを管理しなくていい世界になったことに単純に感動しました。
このブラックボックスを毛嫌いする人はもちろんいそうだけど、個人的にはこれで十分なんじゃないかと思います。
大規模なプロジェクトでも採用実績はあるわけだし、これはインフラの第一選択肢になってしかるべきツールだと思いました。
GAEに付随して、Datastoreだったり、TaskQueueだったり、GAEの中のサービスもいくつか業務で使いながら理解することができました。
NoSQLのデータ設計はRDBMSの時とは全く違っていて、最初はとっつきづらかったです。
まだまだ深く触っていないツールもありますが、いわゆるフルマネージドな環境を使うことに慣れることができました。
API開発全般 json-schemaだったりAPIのリソース設計だったりといったことに本格的に触れました。
今までも経験してきたことではありますが、プラットフォーム開発をする上で必要となること(主に開発以外の部分ですが)を経験できたのは業務の幅が広がったと思います。

個人的トピック

好きな言語ができたこと

まだまだ経験が浅く、自分のエンジニアとしてのキャリアそのものに自信がなかったので、「言語や技術は問わない」ことを強みと思ってましたが、Goを書き始めて今まで感じていた課題感を全て真っ正面から解決されてしまい、ずっぽりこの言語にはまってしまいました。
言語にこだわりがあること自体はネガティブな捉え方をされることがしばしばありますが、もし、好きな技術を選択できるならGoを選びたいですし、働くならGoを書いて働きたいと思えるようになったは、進歩なのかもしれません。
(もちろん技術にこだわりがないことに変わりはありませんが、できることならGo書きたいなと思っています。)

個人でも何か作るときはほとんどGoを選んで使うことが多いです。

プラットフォーム開発を経験できた(現在進行形でしている)こと

プラットフォーム戦略的なことはよく聞くし、そこを目指しているサービスは星の数ほどあると思います。
今までも「〇〇のプラットフォームになる!」みたいな言葉をたくさん聞いてきたし、それは前職でも変わらずみんな言いたがることではあったと思ってます。
ただ、一開発者として、いざそれを作ろうと思って、「それ、どう作るの?」、「何が必要なの?」、みたいなことから「何をもってプラットフォームなのか?」みたいなことを全く知らなかったことに気づき、その中でプラットフォームの開発に関われたのは個人的にはすごくよかったです。
(今年発売されたこの本が非常に参考になったのは言うまでもありません。。。)

1サービスを開発しているときとは比べものにならないほどのステークホルダーの数と、調整、資料準備、コミュニケーションの数々、最初は面食らいましたが、でもこれらは全て自分がエンジニアのキャリアの中でしてこなかったことであり、今ここで身につけるべきキャリアなのかなと思って、技術以外のこともかなり時間を使っていました。

特にめちゃめちゃコンフル書いてました。ここ半年考えたら wiki:コード = 6:4くらいの比率なんじゃないかと思ってます。
多分これからもこれは続いていくんだと思います。

綺麗なコンフルの書き方とかすごい勉強しましたし、自身の文章のわかりづらさとかトンマナの不揃い感とか身にしみました。。。

ちゃんとブログ書くようになったこと

これは上に記載している、wiki力をあげる一環でちゃんと書くようにしました。
コード書く力も求められますが、文章書く力が実は地味に必要であることに、今年途中くらいから気づき始めたので、人の目に触れるものを書く練習しようと思ったのがことの発端です。

まずは、内容よりも書くことを習慣づけるために、個人の備忘録とか、ちょっと触って見たツールとかちまちま書いてました。
この傾向はしばらく続くだろうと思います笑。

ただ、アウトプットし始めたら、副産物でgithub整備するようになったし、本だけでなく、海外の記事等も時間見つけて読むように最近なってきたので、実はいい循環じゃないかと思っています。
あと、今更ですが、タイピング速度が少し上がりました。これは資料作成においては単純な効率UPに繋がってます笑。

まずは書くこと、ちゃんとした文章を素早く書くことができることを念頭においているので、あまり書いてて疲れないです。
来年はもう少しちゃんと質の高いエントリにも挑戦したいです。

総括

年度始めにアプリ方面に関わっていきたいという目標を立てたけど、結果的にはどっぷりサーバーサイド沼に浸かった1年でした。(年度って書いてあるからちょっと時期ずれます...)

ema-hiro.hatenablog.com

とにかくたくさんのことがあった1年だったし、キャリアとしては現在の開発チームに入ってからエンジニアというものを1から学び直している実感があって、しんどいことがほぼ大半でした。
3年間エンジニアを続けてきたけど、まだまだ自分なんてひよっこの部類で、しないといけないこと、できるようにならないといけないことがたくさんあって、それが実は技術に寄らないことまで広がっていて、本当に「ものつくり」は奥が深いと実感している毎日です。
技術の奥深さ、サーバーサイドの奥深さ、プラットフォーム開発の奥深さ、そしてなによりチームでのもの作りの面白さ、しんどさを痛感したので、来年はもう少し苦労しようかなと思います。

これからはgoのstreamを使っていこうと思った話

これからgoでjson形式のデータをstructにmappingする際にはstreamを使うことを決意した話です。

経緯

下記のエントリーに触発されました。

christina04.hatenablog.com

goでjsonをstructにmappingすることをはじめ、 io.Reader のデータを扱うときは一度 []byte 型に変換してから諸々の処理を行うというのが一般的な実装方法です。

一般的なjsonのmapping

APIのレスポンスなど、goにおいて io.Reader 型を受け取り、[]byte に変換してからjsonのデータ構造をオブジェクトにmappingするときの一般的なコードは下のような形式になるかと思います。

client := http.Client{}
resp, err := client.Get(url)
if err != nil {
  fmt.Printf("Error: get error. err: %v", err)
  return nil, err
}

body := resp.Body()
defer body.Close()

b, err := ioutil.ReadAll(body)
if err != nil {
  panic(err)
}

var data SomeData
if err := json.Unmarshal(b, &data); err != nil {
  panic(err)
}

一般的な上記のコードですが、byte型に変換する上で上述のエントリに記載されているようなメモリ効率が悪い点に加えて、

  • httpリクエストのとき
  • []byteに変換するとき
  • jsonをDecodeするとき

の計3回エラーハンドリングの処理を書かなければならず、コードがその分長くなるのが個人的にはちょっと冗長になっていると感じます。

streamを使う

[]byte 型に変換して json.Unmarshal する方法を使わずstreamを利用したコードは以下

client := http.Client{}
resp, err := client.Get(url)
if err != nil {
  fmt.Printf("Error: get error. err: %v", err)
  return nil, err
}

defer resp.Body.Close()
var data SomeData
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
  panic(err)
}

[]byte型に変換せずに json.NewDecoder(io.Reader) にそのまま io.Reader型のbodyを渡して、decodeします。

streamを利用すると、 []byte型に変換する箇所が丸々なくなるので、ここでエラーハンドリングの回数が一回減って、コードも見通しがよくなります。

cf. エントリ内で参照されている

stackoverflow.com

の中で言及されていますが、

So a better rule of thumb is this:

Use json.Decoder if your data is coming from an io.Reader stream, or you need to decode multiple values from a stream of data.
Use json.Unmarshal if you already have the JSON data in memory.

For the case of reading from an HTTP request, I'd pick json.Decoder since you're obviously reading from a stream.

  1. 単一のio.Readerからデータを取得した場合か単一streamにおいて複数の値をDecodeする場合 -> json.Decoderを使うべき
  2. すでにmemoryにjsonのデータを保持している場合 -> json.Unmarshal を使うべき

とされています。
APIからのレスポンスを取得するようなユースケースにおいては最初からmemoryにjsonのデータが保持されているわけではないので、 json.Decoder の方が適しているということですね。

実際に書き換えてみた

以前作った下記のリポジトリxmlをDecodeする箇所をstreamで書き換えてみました。

github.com

https://github.com/emahiro/golang-hatena-client/commit/c1ffbc8bed30a2f526d19a616dc01d360889c78b のコミットで書き換えてます。

まとめ

これからは、io.Reader をinterfaceとして持つ io.ReadCloser 型のBodyなど、streamを使える場合は積極的にstreamのまま使っていこうと思います。
また、io.Reader 型を生成して、使い回すというテクニックも覚えていこうと思います。

例えば....

  • zip.NewReadergzip.NewReader のような NewReader のインターフェースを持つ場合は積極的に、io.Reader型に変換するなど

goでAPIのスケルトンを作成する

以前書いた以下のエントリーからさらい一歩進めました。

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

内容は net/httpgorilla/mux を使ってroutingを作る超薄いAPIサーバーを作成する際にこれだけコピれば簡単にapiサーバーを立てることができるみたいなものを作ることです。
rubyでいうsinatra的なものを作りたかった感じです。

net/httpを使うパターン

ディレクトリ構造

go_diet_api_skelton
  src
    go_diet_api_skelton
      handler/
        root.go
      web/
        router.go
      main.go

実際のコードは以下

github.com

/ にアクセスしてみる

$ curl -i http://localhost:808012/23 09:51:22 2017
HTTP/1.1 200 OK
Date: Sat, 23 Dec 2017 00:51:33 GMT
Content-Length: 11
Content-Type: text/plain; charset=utf-8

Hello World

goの標準機能だけで作成するどシンプルなAPIサーバーだと思います。
goの net/http が非常にシンプルで強力なので、正直これくらいシンプルな方がいいのではと思います。

gorilla/muxを使う

ディレクトリ構造

ディレクトリ構造は同じ

go_diet_api_skelton
  src
    go_diet_api_skelton
      handler/
        root.go
      web/
        router.go
      main.go

gorilla/mux を入れる。

$ dep ensure -add github/gorilla/mux

実際に書いたコードは以下

github.com

/ にアクセスしてみる

$ curl -i http://localhost:808012/23 09:51:22 2017
HTTP/1.1 200 OK
Date: Sat, 23 Dec 2017 00:51:33 GMT
Content-Length: 11
Content-Type: text/plain; charset=utf-8

Hello World

gorillaの方が以下のような感じで、routingでmethod等を指定できるので便利です。

m.HandleFunc("/", handler.Root).Methods("GET")

まとめ

以前のエントリーでは、routingまで main.go に記載していました。
しかし、main.goは薄く保ちつつ、routingの責務は router.go に負わせた方が適切だと考え、いっそのこと、汎用的に使える雛形が欲しいと思ったので、2パターンで書いてみました。

GolangではてなのRSSを取得するクライアントを作成する

仕様

  • はてなRSSのfeedのURLを登録する
  • 登録したURLでRSSを取得する
  • RSSをparseする

実装

sampleでは、はてなの「テクノロジー」のホットエントリーを使用する。 http://b.hatena.ne.jp/hotentry/it.rss

実際に叩いてみる

$ curl -i http://b.hatena.ne.jp/hotentry/it.rss
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns="http://purl.org/rss/1.0/"
 xmlns:content="http://purl.org/rss/1.0/modules/content/"
 xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
 xmlns:opensearch="http://a9.com/-/spec/opensearchrss/1.0/"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"
 xmlns:media="http://search.yahoo.com/mrss"
>
  <channel rdf:about="http://b.hatena.ne.jp/hotentry/it">
    <title>はてなブックマーク - 人気エントリー - テクノロジー</title>
    <link>http://b.hatena.ne.jp/hotentry/it</link>
    <description>最近の人気エントリー - テクノロジー</description>
    <items>
      <rdf:Seq>
        <rdf:li rdf:resource="https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4" />
        <rdf:li rdf:resource="http://tech.mercari.com/entry/2017/12/18/deadlock" />
        <rdf:li rdf:resource="http://brevis.exblog.jp/26270824/" />
        <rdf:li rdf:resource="http://www.pfu.fujitsu.com/direct/hhkb/hhkb-option/detail_pz-kbbrg.html" />
        <rdf:li rdf:resource="http://japanese.engadget.com/2017/12/18/gif-beam-amoled-24/" />
        <rdf:li rdf:resource="https://speakerdeck.com/hihihiroro/kubernetesniru-men-sitai" />
        <rdf:li rdf:resource="http://coliss.com/articles/freebies/best-free-icons-2017.html" />
        <rdf:li rdf:resource="https://note.mu/macheri_me/n/nf2447d4a6167" />
        <rdf:li rdf:resource="http://fushiroyama.hatenablog.com/entry/2017/12/17/225544" />
        <rdf:li rdf:resource="https://tech.raksul.com/2017/12/18/raksul-platform-project/" />
        <rdf:li rdf:resource="https://qiita.com/advent-calendar/2017/ubiregi" />
        <rdf:li rdf:resource="https://www.894651.com/column/craftman_004" />
        <rdf:li rdf:resource="https://anond.hatelabo.jp/20171218212227" />
        <rdf:li rdf:resource="https://forest.watch.impress.co.jp/docs/serial/yajiuma/1097447.html" />
        <rdf:li rdf:resource="https://game.watch.impress.co.jp/docs/news/1097558.html" />
        <rdf:li rdf:resource="http://www.yukisako.xyz/entry/start_arduino" />
        <rdf:li rdf:resource="http://eng-blog.iij.ad.jp/archives/1188" />
        <rdf:li rdf:resource="https://qiita.com/bokuweb/items/1575337bef44ae82f4d3" />
        <rdf:li rdf:resource="https://fullswing.dena.com/slack/" />
        <rdf:li rdf:resource="https://developers.eure.jp/tech/primer_of_sql/" />
        <rdf:li rdf:resource="https://medium.com/@deeeet/7cf4280d435b" />
        <rdf:li rdf:resource="https://shugo.net/jit/20171215.html#p01" />
        <rdf:li rdf:resource="https://gigazine.net/news/20171218-programming-atcoder/" />
        <rdf:li rdf:resource="http://www.itmedia.co.jp/news/articles/1712/18/news100.html" />
        <rdf:li rdf:resource="http://portal.nifty.com/kiji/171218201494_1.htm" />
        <rdf:li rdf:resource="https://qiita.com/sawara125/items/67a144801e3c2f09f53a" />
        <rdf:li rdf:resource="http://twitter.com/yoppymodel/status/942592739270467584" />
        <rdf:li rdf:resource="https://japan.cnet.com/article/35112083/" />
        <rdf:li rdf:resource="https://qiita.com/karupanerura/items/e90bba7166878ece9f06" />
        <rdf:li rdf:resource="https://codezine.jp/article/detail/10591" />
      </rdf:Seq>
    </items>
  </channel>
  <item rdf:about="https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4">
    <title>仮想通貨自動取引入門 - Qiita</title>
    <link>https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4</link>
    <description>本記事は U-TOKYO AP Advent Calendar 2017 の17日目です。 はじめに 年の瀬が近づき何かと出費がかさむ季節になりましたね。財布の中も真冬です。 実は2ヶ月ほど前から年越しに備えて仮想通貨で資産運用をしています。 他の資産運用と比べたときの仮想通貨取引のメリットは「少額でも大きな利益を得るチャンスがあること」と「24時間365日取引ができること」でしょうか。 というこ...</description>
    <content:encoded>&lt;blockquote cite=&quot;https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4&quot; title=&quot;仮想通貨自動取引入門 - Qiita&quot;&gt;&lt;cite&gt;&lt;img src=&quot;http://cdn-ak.favicon.st-hatena.com/?url=https%3A%2F%2Fqiita.com%2Fshionhonda%2Fitems%2Fbd2a7aaf143eff4972c4&quot; alt=&quot;&quot; /&gt; &lt;a href=&quot;https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4&quot;&gt;仮想通貨自動取引入門 - Qiita&lt;/a&gt;&lt;/cite&gt;&lt;p&gt;本記事は U-TOKYO AP Advent Calendar 2017 の17日目です。 はじめに 年の瀬が近づき何かと出費がかさむ季節になりましたね。財布の中も真冬です。 実は2ヶ月ほど前から年越しに備えて仮想通貨で資産運用をしています。 他の資産運用と比べたときの仮想通貨取引のメリットは「少額でも大きな利益を得るチャンスがあること」と「24時間365日取引ができること」でしょうか。 というこ...&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://b.hatena.ne.jp/entry/https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4&quot;&gt;&lt;img src=&quot;http://b.hatena.ne.jp/entry/image/https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4&quot; alt=&quot;はてなブックマーク - 仮想通貨自動取引入門 - Qiita&quot; title=&quot;はてなブックマーク - 仮想通貨自動取引入門 - Qiita&quot; border=&quot;0&quot; style=&quot;border: none&quot; /&gt;&lt;/a&gt; &lt;a href=&quot;http://b.hatena.ne.jp/append?https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4&quot;&gt;&lt;img src=&quot;http://b.hatena.ne.jp/images/append.gif&quot; border=&quot;0&quot; alt=&quot;はてなブックマークに追加&quot; title=&quot;はてなブックマークに追加&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;</content:encoded>
    <dc:date>2017-12-18T16:22:14+09:00</dc:date>
    <dc:subject>テクノロジー</dc:subject>
    <hatena:bookmarkcount>120</hatena:bookmarkcount>
  </item>
# 略
</rdf:RDF>

I/F

RSSを取得するだけなので、クエリパラメータでURLを渡してあげようと思います。

$ curl -i -X GET http://localhost:8080/rss?url=http://b.hatena.ne.jp/hotentry/it.rss

みたいなGetのURLを叩けばいいですね。

Parseする

構造的には以下です。

channel
  title
  link
  ...
item
item

#略

rssをParseすると言っても、実態はxmlで返ってくるデータ構造をparseすることと同じ。
以前AmazonAPIで遊んだ時と同様に、 encoding/xml を使います。 前回は愚直にxmlの構造をstructに起こしましたが、xmlをparseする時は親子構造を > 使って表現してparseすることができます。

以下のようなstructを定義してrssのfeedをマッピングします。

package model

import (
    "encoding/xml"
)

type HatenaFeed struct {
    XMLName         xml.Name         `xml:"RDF"`
    Title           string           `xml:"channel>title"`
    Link            string           `xml:"channel>link"`
    Description     string           `xml:"channel>description"`
    HatenaBookmarks []HatenaBookmark `xml:"item"`
}

type HatenaBookmark struct {
    XMLName       xml.Name `xml:"item"`
    Title         string   `xml:"title"`
    Link          string   `xml:"link"`
    Description   string   `xml:"description"`
    Content       string   `xml:"content"`
    Date          string   `xml:"date"`
    Subject       string   `xml:"subject"`
    BookmarkCount int64    `xml:"bookmarkcount"`
}

これでfeedを取得したのち、レスポンスをbyte型に変換して xml.Unmarshal でparseすることで以下のような構造のオブジェクトを取得できます。

$~/e/golang-hatena-client  go run src/golang-hatena-client/main.go                                                                                 火 12/19 03:45:02 2017
&model.HatenaFeed{
  XMLName: xml.Name{
    Space: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    Local: "RDF",
  },
  Title:           "はてなブックマーク - 人気エントリー - テクノロジー",
  Link:            "http://b.hatena.ne.jp/hotentry/it",
  Description:     "最近の人気エントリー - テクノロジー",
  HatenaBookmarks: []model.HatenaBookmark{
    model.HatenaBookmark{
      XMLName: xml.Name{
        Space: "http://purl.org/rss/1.0/",
        Local: "item",
      },
      Title:         "仮想通貨自動取引入門 - Qiita",
      Link:          "https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4",
      Description:   "本記事は U-TOKYO AP Advent Calendar 2017 の17日目です。 はじめに 年の瀬が近づき何かと出費がかさむ季節になりましたね。財布の中も真冬です。 実は2ヶ月ほど前から年越しに備えて仮想通貨で資産運用をしています。 他の資産運用と比べたときの仮想通貨取引のメリットは「少額でも大きな利益を得るチャンスがあること」と「24時間365日取引ができること」でしょうか。 というこ...",
      Content:       "",
      Date:          "2017-12-18T16:22:14+09:00",
      Subject:       "テクノロジー",
      BookmarkCount: 174,
    },
# 略

今回の実装ではクエリパラメータに入るURLははてなのURLであればなんでも入れてRSSを取得できます。

書いたコードはこちら

github.com

goでmecabを動かす

簡単な分かち書きCLIツール作ってみようと思ったのでmecabをgoで動かして見ました。

やったこと

  • goでmecabを動かすこと

mecab-golangのinstall

以下のmecabのクライアントを使いました。

github.com

最初に

mecab をinstallし、mecab-config が入っているかを確認します。

$ brew search mecab
==> Searching local taps...
mecab
mecab-ipadic
# 以下色々
$ brew insatll mecab mecab-ipadic
$ which mecab-config
/usr/local/bin/mecab-config # 入っている!

golang-mecabを動かします。

GOPATHをリポジトリに合わせたかったので、direnvを使いました。
※ 通常はGOPATH配下にアプリケーションリポジトリを作るかもですが。

READMEに記載してあったmecabを使うためのライブラリのpathを .envrc に追記します。

export GOPATH=$(pwd)
export CGO_LDFLAGS="`mecab-config --libs`"
export CGO_CFLAGS="-I`mecab-config --inc-dir`"

depgolang-mecab を入れます。

$ dep init
$ dep ensure
$ dep ensure -add github.com/bluele/mecab-golang

これで mecab-golang が動くようになります。

実際に書いたコード

package main

import (
    "fmt"
    "os"
    "strings"

    "github.com/bluele/mecab-golang"
)

func parseToNode(m *mecab.MeCab, input string) {
    tg, err := m.NewTagger()
    if err != nil {
        fmt.Printf("NewTagger error. err: %v", err)
        os.Exit(-1)
    }
    defer tg.Destroy()

    lt, err := m.NewLattice(input)
    if err != nil {
        fmt.Printf("NewLattice error. err: %v", err)
        os.Exit(-1)
    }
    defer lt.Destroy()

    node := tg.ParseToNode(lt)
    for {
        features := strings.Split(node.Feature(), ",")
        fmt.Printf("features: %v\n", features)
        if node.Next() != nil {
            break
        }
    }
}

func main() {
    var input string
    fmt.Println("---- Input your text below ----")
    fmt.Scan(&input)
    m, err := mecab.New("-Owakati")
    if err != nil {
        fmt.Printf("Mecab instance error. err: %v", err)
    }
    defer m.Destroy()

    // parse to node
    parseToNode(m, input)

    fmt.Printf("%v", "Complete !!!")
}

動かしてみる。

$ go run main.go
---- Input your text below ----
こんにちは佐藤さん
features: [BOS/EOS * * * * * * * *]
features: [感動詞 * * * * * こんにちは コンニチハ コンニチワ]
features: [名詞 固有名詞 人名 姓 * * 佐藤 サトウ サトー]
features: [名詞 接尾 人名 * * * さん サン サン]
features: [BOS/EOS * * * * * * * *]
Complete !!!⏎

ちゃんと動いている!

今回書いたコードは以下

github.com

Amazon Product Advertising APIのレスポンスをパースする

やったこと

Amazon Product Advertising APIを使ってISBNから書籍情報を取得するプログラムを書く際に、AmazonAPIのレスポンスは json ではなく、xmlでデータが帰ってくるので、このxmlGolangでparseするためにしました。

※ 今時xmlかよ!っと思ってちょっとびっくりしたのは秘密。

Parse XML Response in Go

Golangに置いて、xmlをparseするには encoding/xml を使います。
jsonをparseするときと同様にデータ構造をstructにMappingする際に、明示的にxmlでのタグを追加します。

ex) Amazon Product Advertising API - Operation - ItemLookup の場合

type ItemLookupResponse struct {
    XMLName xml.Name `xml:"ItemLookupResponse"` // ここ!!
    Items   Items    `xml:"Items"`
}

Sample

Amazon Product Advertising APIを叩いてレスポンス(xml)を取得します。
取得の方法は以前Amazon Product Advertising APIを叩いた時に書いた以下の記事を参考にしてください。

ema-hiro.hatenablog.com

ResponseGroup は ItemAttributes を指定しています。
※ 書籍は同僚でいつもお世話になっているyoichiro san の最新の著書を使わせてもらってます。

"<?xml version=\"1.0\" ?><ItemLookupResponse xmlns=\"http://webservices.amazon.com/AWSECommerceService/2011-08-01\"><OperationRequest><HTTPHeaders><Header Name=\"UserAgent\" Value=\"Go-http-client/1.1\"></Header></HTTPHeaders><RequestId>e715307c-950e-4a5f-80d8-bc90d09c0ce9</RequestId><Arguments><Argument Name=\"AWSAccessKeyId\" Value=\"AKIAI43PAKG3WCIFW2DQ\"></Argument><Argument Name=\"AssociateTag\" Value=\"emahiro-22\"></Argument><Argument Name=\"IdType\" Value=\"ISBN\"></Argument><Argument Name=\"ItemId\" Value=\"9784774193328\"></Argument><Argument Name=\"Operation\" Value=\"ItemLookup\"></Argument><Argument Name=\"ResponseGroup\" Value=\"ItemAttributes\"></Argument><Argument Name=\"SearchIndex\" Value=\"Books\"></Argument><Argument Name=\"Service\" Value=\"AWSECommerceService\"></Argument><Argument Name=\"Timestamp\" Value=\"2017-12-11T17:25:44Z\"></Argument><Argument Name=\"Signature\" Value=\"Ak5Lz77ab+aGuBgz9eLuPRMqP092drq+Yqmp455DlAA=\"></Argument></Arguments><RequestProcessingTime>0.0137283900000000</RequestProcessingTime></OperationRequest><Items><Request><IsValid>True</IsValid><ItemLookupRequest><IdType>ISBN</IdType><ItemId>9784774193328</ItemId><ResponseGroup>ItemAttributes</ResponseGroup><SearchIndex>Books</SearchIndex><VariationPage>All</VariationPage></ItemLookupRequest></Request><Item><ASIN>4774193321</ASIN><DetailPageURL>https://www.amazon.co.jp/%E3%82%BD%E3%83%BC%E3%82%B7%E3%83%A3%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E6%A7%8B%E7%AF%89%E6%8A%80%E6%B3%95%E2%80%95%E2%80%95SNS%E3%81%8B%E3%82%89BOT%E3%81%BE%E3%81%A7IT%E3%82%92%E3%82%B3%E3%82%A2%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E4%BC%81%E6%A5%AD%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-Software-Design-plus%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E6%B4%8B%E4%B8%80%E9%83%8E/dp/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=165953&amp;creativeASIN=4774193321</DetailPageURL><ItemLinks><ItemLink><Description>Add To Wishlist</Description><URL>https://www.amazon.co.jp/gp/registry/wishlist/add-item.html?asin.0=4774193321&amp;SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL></ItemLink><ItemLink><Description>Tell A Friend</Description><URL>https://www.amazon.co.jp/gp/pdp/taf/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL></ItemLink><ItemLink><Description>All Customer Reviews</Description><URL>https://www.amazon.co.jp/review/product/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL></ItemLink><ItemLink><Description>All Offers</Description><URL>https://www.amazon.co.jp/gp/offer-listing/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL></ItemLink></ItemLinks><ItemAttributes><Author>田中 洋一郎</Author><Binding>単行本(ソフトカバー)</Binding><EAN>9784774193328</EAN><EANList><EANListElement>9784774193328</EANListElement></EANList><IsAdultProduct>0</IsAdultProduct><ISBN>4774193321</ISBN><Label>技術評論社</Label><Languages><Language><Name>日本語</Name><Type>Published</Type></Language></Languages><Manufacturer>技術評論社</Manufacturer><NumberOfPages>360</NumberOfPages><PackageDimensions><Height Units=\"100分の1インチ\">71</Height><Length Units=\"100分の1インチ\">835</Length><Weight Units=\"100分の1ポンド\">97</Weight><Width Units=\"100分の1インチ\">591</Width></PackageDimensions><ProductGroup>Book</ProductGroup><ProductTypeName>ABIS_BOOK</ProductTypeName><PublicationDate>2017-10-20</PublicationDate><Publisher>技術評論社</Publisher><Studio>技術評論社</Studio><Title>ソーシャルアプリプラットフォーム構築技法――SNSからBOTまでITをコアに成長する企業の教科書 (Software Design plusシリーズ)</Title></ItemAttributes></Item><Item><ASIN>B076GXMNFN</ASIN><DetailPageURL>https://www.amazon.co.jp/%E3%82%BD%E3%83%BC%E3%82%B7%E3%83%A3%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E6%A7%8B%E7%AF%89%E6%8A%80%E6%B3%95-%E2%80%95%E2%80%95SNS%E3%81%8B%E3%82%89BOT%E3%81%BE%E3%81%A7IT%E3%82%92%E3%82%B3%E3%82%A2%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E4%BC%81%E6%A5%AD%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-Software-Design-plus-ebook/dp/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=165953&amp;creativeASIN=B076GXMNFN</DetailPageURL><ItemLinks><ItemLink><Description>Add To Wishlist</Description><URL>https://www.amazon.co.jp/gp/registry/wishlist/add-item.html?asin.0=B076GXMNFN&amp;SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL></ItemLink><ItemLink><Description>Tell A Friend</Description><URL>https://www.amazon.co.jp/gp/pdp/taf/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL></ItemLink><ItemLink><Description>All Customer Reviews</Description><URL>https://www.amazon.co.jp/review/product/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL></ItemLink><ItemLink><Description>All Offers</Description><URL>https://www.amazon.co.jp/gp/offer-listing/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL></ItemLink></ItemLinks><ItemAttributes><Author>田中 洋一郎</Author><Binding>Kindle版</Binding><EISBN>9784774193687</EISBN><Format>Kindle本</Format><IsAdultProduct>0</IsAdultProduct><Label>技術評論社</Label><Languages><Language><Name>日本語</Name><Type>Published</Type></Language></Languages><Manufacturer>技術評論社</Manufacturer><NumberOfPages>602</NumberOfPages><ProductGroup>eBooks</ProductGroup><ProductTypeName>ABIS_EBOOKS</ProductTypeName><PublicationDate>2017-10-20</PublicationDate><Publisher>技術評論社</Publisher><ReleaseDate>2017-10-20</ReleaseDate><Studio>技術評論社</Studio><Title>ソーシャルアプリプラットフォーム構築技法 ――SNSからBOTまでITをコアに成長する企業の教科書 Software Design plus</Title></ItemAttributes></Item></Items></ItemLookupResponse>"

ItemAttributesResponseGroup として指定した時の上記のようなxmlが返ってきても、生のxmlは可読性が悪いので、以下のようなサービスを使ってresponseのxmlを見やすい形に整形して、一つ一つ構造体にmappingしていきます。

http://u670.com/pikamap/htmlseikei.php

<?xml version=\"1.0\" ?>
<ItemLookupResponse xmlns=\"http://webservices.amazon.com/AWSECommerceService/2011-08-01\">
  <OperationRequest>
    <HTTPHeaders>
      <Header Name=\"UserAgent\" Value=\"Go-http-client/1.1\"></Header>
    </HTTPHeaders>
    <RequestId>e7d362b0-abe9-42a1-9cc1-ef9943303e12</RequestId>
    <Arguments>
      <Argument Name=\"AWSAccessKeyId\" Value=\"AKIAI43PAKG3WCIFW2DQ\"></Argument>
      <Argument Name=\"AssociateTag\" Value=\"emahiro-22\"></Argument>
      <Argument Name=\"IdType\" Value=\"ISBN\"></Argument>
      <Argument Name=\"ItemId\" Value=\"9784774193328\"></Argument>
      <Argument Name=\"Operation\" Value=\"ItemLookup\"></Argument>
      <Argument Name=\"ResponseGroup\" Value=\"ItemAttributes\"></Argument>
      <Argument Name=\"SearchIndex\" Value=\"Books\"></Argument>
      <Argument Name=\"Service\" Value=\"AWSECommerceService\"></Argument>
      <Argument Name=\"Timestamp\" Value=\"2017-12-12T16:28:21Z\"></Argument>
      <Argument Name=\"Signature\" Value=\"MGrC8pmelta5xLNmwDTSH3HdmksJvE5PieDTu9lOqzE=\"></Argument>
    </Arguments>
    <RequestProcessingTime>0.0108954000000000</RequestProcessingTime>
  </OperationRequest>
  <Items>
    <Request>
      <IsValid>True</IsValid>
      <ItemLookupRequest>
        <IdType>ISBN</IdType>
        <ItemId>9784774193328</ItemId>
        <ResponseGroup>ItemAttributes</ResponseGroup>
        <SearchIndex>Books</SearchIndex>
        <VariationPage>All</VariationPage>
      </ItemLookupRequest>
    </Request>
    <Item>
      <ASIN>4774193321</ASIN>
      <DetailPageURL>https://www.amazon.co.jp/%E3%82%BD%E3%83%BC%E3%82%B7%E3%83%A3%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E6%A7%8B%E7%AF%89%E6%8A%80%E6%B3%95%E2%80%95%E2%80%95SNS%E3%81%8B%E3%82%89BOT%E3%81%BE%E3%81%A7IT%E3%82%92%E3%82%B3%E3%82%A2%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E4%BC%81%E6%A5%AD%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-Software-Design-plus%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E6%B4%8B%E4%B8%80%E9%83%8E/dp/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=165953&amp;creativeASIN=4774193321</DetailPageURL>
      <ItemLinks>
        <ItemLink>
          <Description>Add To Wishlist</Description>
          <URL>https://www.amazon.co.jp/gp/registry/wishlist/add-item.html?asin.0=4774193321&amp;SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL>
        </ItemLink>
        <ItemLink>
          <Description>Tell A Friend</Description>
          <URL>https://www.amazon.co.jp/gp/pdp/taf/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL>
        </ItemLink>
        <ItemLink>
          <Description>All Customer Reviews</Description>
          <URL>https://www.amazon.co.jp/review/product/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL>
        </ItemLink>
        <ItemLink>
          <Description>All Offers</Description>
          <URL>https://www.amazon.co.jp/gp/offer-listing/4774193321?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=4774193321</URL>
        </ItemLink>
      </ItemLinks>
      <ItemAttributes>
        <Author>田中 洋一郎</Author>
        <Binding>単行本(ソフトカバー)</Binding>
        <EAN>9784774193328</EAN>
        <EANList>
          <EANListElement>9784774193328</EANListElement>
        </EANList>
        <IsAdultProduct>0</IsAdultProduct>
        <ISBN>4774193321</ISBN>
        <Label>技術評論社</Label>
        <Languages>
          <Language>
            <Name>日本語</Name>
            <Type>Published</Type>
          </Language>
        </Languages>
        <Manufacturer>技術評論社</Manufacturer>
        <NumberOfPages>360</NumberOfPages>
        <PackageDimensions>
          <Height Units=\"100分の1インチ\">71</Height>
          <Length Units=\"100分の1インチ\">835</Length>
          <Weight Units=\"100分の1ポンド\">97</Weight>
          <Width Units=\"100分の1インチ\">591</Width>
        </PackageDimensions>
        <ProductGroup>Book</ProductGroup>
        <ProductTypeName>ABIS_BOOK</ProductTypeName>
        <PublicationDate>2017-10-20</PublicationDate>
        <Publisher>技術評論社</Publisher>
        <Studio>技術評論社</Studio>
        <Title>ソーシャルアプリプラットフォーム構築技法――SNSからBOTまでITをコアに成長する企業の教科書 (Software Design plusシリーズ)</Title>
      </ItemAttributes>
    </Item>
    <Item>
      <ASIN>B076GXMNFN</ASIN>
      <DetailPageURL>https://www.amazon.co.jp/%E3%82%BD%E3%83%BC%E3%82%B7%E3%83%A3%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E6%A7%8B%E7%AF%89%E6%8A%80%E6%B3%95-%E2%80%95%E2%80%95SNS%E3%81%8B%E3%82%89BOT%E3%81%BE%E3%81%A7IT%E3%82%92%E3%82%B3%E3%82%A2%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E4%BC%81%E6%A5%AD%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-Software-Design-plus-ebook/dp/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=165953&amp;creativeASIN=B076GXMNFN</DetailPageURL>
      <ItemLinks>
        <ItemLink>
          <Description>Add To Wishlist</Description>
          <URL>https://www.amazon.co.jp/gp/registry/wishlist/add-item.html?asin.0=B076GXMNFN&amp;SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL>
        </ItemLink>
        <ItemLink>
          <Description>Tell A Friend</Description>
          <URL>https://www.amazon.co.jp/gp/pdp/taf/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL>
        </ItemLink>
        <ItemLink>
          <Description>All Customer Reviews</Description>
          <URL>https://www.amazon.co.jp/review/product/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL>
        </ItemLink>
        <ItemLink>
          <Description>All Offers</Description>
          <URL>https://www.amazon.co.jp/gp/offer-listing/B076GXMNFN?SubscriptionId=AKIAI43PAKG3WCIFW2DQ&amp;tag=emahiro-22&amp;linkCode=xm2&amp;camp=2025&amp;creative=5143&amp;creativeASIN=B076GXMNFN</URL>
        </ItemLink>
      </ItemLinks>
      <ItemAttributes>
        <Author>田中 洋一郎</Author>
        <Binding>Kindle版</Binding>
        <EISBN>9784774193687</EISBN>
        <Format>Kindle本</Format>
        <IsAdultProduct>0</IsAdultProduct>
        <Label>技術評論社</Label>
        <Languages>
          <Language>
            <Name>日本語</Name>
            <Type>Published</Type>
          </Language>
        </Languages>
        <Manufacturer>技術評論社</Manufacturer>
        <NumberOfPages>602</NumberOfPages>
        <ProductGroup>eBooks</ProductGroup>
        <ProductTypeName>ABIS_EBOOKS</ProductTypeName>
        <PublicationDate>2017-10-20</PublicationDate>
        <Publisher>技術評論社</Publisher>
        <ReleaseDate>2017-10-20</ReleaseDate>
        <Studio>技術評論社</Studio>
        <Title>ソーシャルアプリプラットフォーム構築技法 ――SNSからBOTまでITをコアに成長する企業の教科書 Software Design plus</Title>
      </ItemAttributes>
    </Item>
  </Items>
</ItemLookupResponse>

構造化されたxmlのレスポンスを取得することができました。 このようなxmlの構造をstructにMappingするために書いたコードが以下にあります。

github.com

ハマったところ

API周りだと、ResponseGroupをいくつか追加で指定することができますが、複数指定する場合には , の後に 半角スペース を入れてはいけということを知りました。
そのため、Itemの情報(ItemAttributes)だけでなく、Itemの画像を知りたい時は Images というResponseGroupを追加することになるのですが、

// OK
params.Set("ResponseGroup", "ItemAttributes,Images")

// NG 
params.Set("ResponseGroup", "ItemAttributes, Images") // 半角スペースが入ってしまっている。

となります。

また、xmlの構造をStructにMappingする時に

type ItemAttributes struct {
    XMLName           xml.Name          `xml:"ItemAttributes"`
    Author            []string          `xml:"Author"`
    Binding           string            `xml:"Binding"`
    EAN               int64             `xml:"EAN"`
    EANList           EANList           `xml:"EANList"`
    IsAdultProduct    bool              `xml:"IsAdultProduct"`
    ISBN              string            `xml:"ISBN"`
    Label             string            `xml:"Label"`
    Languages         Languages         `xml:"Languages"`
    Manufacturer      string            `xml:"Manufacturer"`
    NumberOfPages     int64             `xml:"NumberOfPages"`
    PackageDimensions PackageDimensions `xml:"PackageDimensions"`
    ProductGroup      string            `xml:"ProductGroup"`
    ProductTypeName   string            `xml:"ProductTypeName"`
    PublicationDate   string            `xml:"PublicationDate"`
    Publisher         string            `xml:"Publisher"`
    Studio            string            `xml:"Studio"`
    Title             string            `xml:"Title"`
}

のような構造において Languages fieldを考える時に、通常なら

type Language struct {
    XMLName xml.Name `xml:"Language"`
    Name    string   `xml:"Name"`
    Type    string   `xml:"Type"`
}

という Language のStruct(Model)を用意して、

// 略
Languages         []Language         `xml:"Languages"`

のように、明示的に配列がデータ構造としてはいることがわかりそうなものですが、上記の記載方法だとUnmarshalする時にエラーになってしまいます。
xmlをParseする際には、以下のような中間Structを定義しないと正確にUnmarshalされないことを知りました。

type Languages struct {
    XMLName  xml.Name   `xml:"Languages"`
    Language []Language `xml:"Language"`
}

Languages タグはあくまで Languages タグであり、その中に Language が配列として入っている、というタグの構造を理解せず、 JsonをMappingするときの感覚だと失敗しました。

追記

xml> を使って親子関係を表現できます。
ItemAttributes のstructの構造において

EANListの構造は以下

EANList
  EANListElement

Languagesの構造は以下

Languages
  []Language
  

という構造を表現するのにstructを二つ作って親子関係を表現していました。 しかし、 > を使って xmlの定義の箇所で親子関係を表現させることが可能です。

なので ItemAttributes のstructは以下のように変更可能になります。

package model

import "encoding/xml"

type ItemAttributes struct {
    XMLName           xml.Name          `xml:"ItemAttributes"`
    Author            []string          `xml:"Author"`
    Binding           string            `xml:"Binding"`
    EAN               int64             `xml:"EAN"`
    EANList           int64             `xml:"EANList>EANListElement"` // ここ
    IsAdultProduct    bool              `xml:"IsAdultProduct"`
    ISBN              string            `xml:"ISBN"`
    Label             string            `xml:"Label"`
    Languages         []Language        `xml:"Languages>Language"` // ここ
    Manufacturer      string            `xml:"Manufacturer"`
    NumberOfPages     int64             `xml:"NumberOfPages"`
    PackageDimensions PackageDimensions `xml:"PackageDimensions"`
    ProductGroup      string            `xml:"ProductGroup"`
    ProductTypeName   string            `xml:"ProductTypeName"`
    PublicationDate   string            `xml:"PublicationDate"`
    Publisher         string            `xml:"Publisher"`
    Studio            string            `xml:"Studio"`
    Title             string            `xml:"Title"`
}

type Language struct {
    XMLName xml.Name `xml:"Language"`
    Name    string   `xml:"Name"`
    Type    string   `xml:"Type"`
}

type PackageDimensions struct {
    XMLName xml.Name `xml:"PackageDimensions"`
    Height  int64    `xml:"Height"`
    Length  int64    `xml:"Length"`
    Weight  int64    `xml:"Weight"`
    Width   int64    `xml:"Width"`
}

xmlは親子関係をgolangで柔軟に表現できるので使い勝手がいいと感じました。

Amazon Product Advertising APIを使ってISBNコードから書籍情報を取得する

やったこと

  • Amazon Product Advertising APIを使って書籍情報を取得すること
  • 書籍情報の検索フックにはISBNコードを使ったこと
  • 利用する際にハマったこと

事前準備

以下は事前にやってあるものとします。

  • Amazon Associate への登録
    • Associate TagにこのIDを使う
  • AcessKeyIdとAccessSecretIdを使う

refs:

blog.apitore.com

実装

※ 書籍のAPIを取得するところのみ抜粋

ディレクトリ構造

- PROJECT
  - conf
    - token_cred.json
  - src
    - project
      - main.go
      - handler/
          - amazon.go

amazon.go の実装は下記

const (
    EC_SERVICE_ENDPOINT = "webservices.amazon.co.jp"
    EC_SERVICE_URI      = "/onca/xml"
)

func readConf() ([]byte, error) {
    f, err := os.Open("./conf/token_cred.json")
    if err != nil {
        fmt.Printf("token_cred.json open error: err; %v", err)
        return nil, err
    }
    b, err := ioutil.ReadAll(f)
    if err != nil {
        fmt.Printf("json file read error: err; %v", err)
        return nil, err
    }
    return b, nil
}

func SearchISBN(w http.ResponseWriter, r *http.Request) {
    b, err := readConf()
    if err != nil {
        fmt.Printf("readConf error. err: %v", err)
        return
    }

    var cred model.AmazonTokenCred
    if err := json.Unmarshal(b, &cred); err != nil {
        fmt.Printf("json unmarshal error. err: %v", err)
        return
    }

    params := url.Values{}
    params.Set("Service", "AWSECommerceService")
    params.Set("Operation", "ItemLookup")
    params.Set("ItemId", "ISBNコード")
    params.Set("IdType", "ISBN")
    params.Set("SearchIndex", "Books")
    params.Set("Timestamp", time.Now().UTC().Format(time.RFC3339))
    params.Set("AWSAccessKeyId", cred.AccessKeyId)
    params.Set("AssociateTag", cred.AssociateTag)
    params.Set("ResponseGroup", "Images,ItemAttributes,Offers")

    // 署名
    canonical_params := params.Encode()
    strToSign := fmt.Sprintf("GET\n%v\n%v\n%v", EC_SERVICE_ENDPOINT, EC_SERVICE_URI, canonical_params)
    mac := hmac.New(sha256.New, []byte(cred.SecretKeyId))
    mac.Write([]byte(strToSign))
    signature := url.QueryEscape(base64.StdEncoding.EncodeToString(mac.Sum(nil)))
    canonical_params = fmt.Sprintf("%v&Signature=%v", canonical_params, signature)

    // http request
    res, err := http.Get(fmt.Sprintf("http://%v%v?%v", EC_SERVICE_ENDPOINT, EC_SERVICE_URI, canonical_params))
    if err != nil {
        fmt.Printf("response error. err: %v", err)
        return
    }
  // response はよしなに整形する
}

ハマったポイント

APIのDocs(https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html?RG_ItemAttributes.html の例のところ)に書いてある遠りのコード書いたら

  • The request must contain the parameter Timestamp.
  • The request must contain the parameter Signature.

の2つのエラーにぶち当たりました。
REST APIのドキュメントをそのまま叩いたのは以下

 curl -i "http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=XXXXXX&Operation=ItemLookup&ItemId=B00008OE6I"
HTTP/1.1 400 Bad Request
Date: Fri, 08 Dec 2017 19:10:17 GMT
Server: Apache-Coyote/1.1
Vary: Accept-Encoding,User-Agent
nnCoection: close
Transfer-Encoding: chunked

<?xml version="1.0"?>
<ItemLookupErrorResponse xmlns="http://ecs.amazonaws.com/doc/2005-10-05/"><Error><Code>MissingParameter</Code><Message>The request must contain the parameter Signature.</Message></Error><RequestID>831754e7-5761-4d0a-adae-6febc205949b</RequestID></ItemLookupErrorResponse>⏎

TimeStampについて

今の時刻をISO8601の形式で使います。
GOにおいては time.RFC3339 で求められてる形式で時刻を取得します。

署名(Signature)について

AmazonProduct Advertising APIで使用する署名(Signature)は 発行したSecretKeyIdを使ってhmacでハッシュ化されたものをbase64でencodeした値 です。

署名の作成は以下のphpのコードを参考にgoで書き直しました。

qiita.com

qiita.com

githubに以下の今回使おうとしているAPIのgoのクライアントを見つけたのでこちらも参考しました。

github.com

署名作成でハマったこと

書いたコードの中で署名を生成している箇所は以下

// 署名
canonical_params := params.Encode()
strToSign := fmt.Sprintf("GET\n%v\n%v\n%v", EC_SERVICE_ENDPOINT, EC_SERVICE_URI, canonical_params)
mac := hmac.New(sha256.New, []byte(cred.SecretKeyId))
mac.Write([]byte(strToSign))
signature := url.QueryEscape(base64.StdEncoding.EncodeToString(mac.Sum(nil)))
canonical_params = fmt.Sprintf("%v&Signature=%v", canonical_params, signature)

url.Values でクエリパラメータをセットして Encode したら完了かと思っていたのですが、上記の署名生成のコードの最後の行で記載しているように Signatureは最後につけないといけない というところで思いっきりハマりました。

例は以下

# params.Encode()でqueryparamsを作成したときのクエリパラメータ
"AWSAccessKeyId=[AccessKeyId]&AssociateTag=[AssociateTag]&IdType=ISBN&ItemId=9784774193328&Operation=ItemLookup&ResponseGroup=Images,ItemAttributes,Offers&SearchIndex=Books&Service=AWSECommerceService&Signature=[生成した署名]&Timestamp=2017-12-10T06:16:16Z"
# -> 403 Forbidden

# 文字列結合でsignatureをつけたときのクエリパラメータ
"AWSAccessKeyId=[AccessKeyId]&AssociateTag=[AssociateTag]&IdType=ISBN&ItemId=9784774193328&Operation=ItemLookup&ResponseGroup=Images,ItemAttributes,Offers&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2017-12-10T06:16:16Z&Signature=[生成した署名]"
# status OK

当初 params.Encode() で文字列作成をしてし待ってましたが、params.Encode() はqueryのkeyを自動でsortしてしまい、 Signature が末尾に来ません。 そのため、params.Encode() でクエリパラメータを生成し、署名内容とurlは文字列結合の時と同じでも、403を返して来てしまいました。

まとめ

Amazon Product Advertising APIは署名を自前で作らないといけなかったりして忘れかけたことを思い出すきっかけをくれたので久しぶりに触ってみてよかったです。
APIのテストで自動的にphpjavaのコードは生成してくれますが、こういうときにgoとかで書き直してみるのもいいなと思います。

とりあえず書いたコードはこちら

github.com

xmlのレスポンスをstructにマッピングしたりはまた次回!

os.Openするときの相対パスの書き方

goで os.Open(filename string) を実行するときに読み取りファイルを特定のディレクトリにまとめておいたときに os.Open で指定する相対パスがどう決まるかのメモです。

答えはmain.goからの相対パスになります。

例えば以下のようなディレクトリ構成のとき

- src
  - main.go
  - conf.json

conf.jsonを読み込むためには

f, err := os.Open("./conf.json")

となります。

次に以下のようなディレクトリ構成を考えます。

- Project
  - conf
    - conf.json
  - src
    - project
      - main.go

この場合は

f, err := os.Open("../../conf/conf.json")

となります。

main.goから見た相対パスだということを忘れていたので備忘録のために書きました。

[追記]

答えはmain.goからの相対パスになります。

ここが少し違っていてこれは実行ディレクトリから見たときの相対パスになります。

- Project
  - conf
    - conf.json
  - src
    - project
      - main.go

のようなディレクトリ構造の時

# ProjectRootで実行する
$ go run src/project/main.go

とする場合はconf以下のjsonを読み込もうと思ったら os.Open(./conf/conf.json) になります。
一方で

# projectディレクトリで実行する
$ go run main.go

と実行する場合は本エントリで書いたような相対パスになります。

どこで実行するかで書き方を変更する必要があります。

選んだ理由よりも「選ばなかった」理由を知りたいなという話

現在働いているチームの目指すべき姿の一つに、答えではなく「観点」を理解する ことで次回以降一人でその答えに辿り着けるようにする。というものがあります。 自分はこの言葉がすごく好きではあるのですが、答えにたどり着く観点の他に、その答えの他に考えていたいくつかの「答え」候補を選ばなかった理由も合わせて知ることが、考える力と判断する力を養う一つのアプローチになるのではないかと考えています。

選ばなかった理由を知ることの例えとして、技術選定のプロセスを考えます。
このプロセスでは 特定の技術を採用する というのが最終的なゴールになります。 特定の技術を採用するということは、候補として上がっていた他の技術を採用しなかった ということになるわけで、その技術たちを どうして採用しなかったのか ということの方が、採用した経緯を知るより学べる事が多いと感じています。

もう一つの例えとしてコードを書くことを考えて見ます。

「コードを書く瞬間の思考」にアドバイスを貰える

at-grandpa.hatenablog.jp

書かなかったコードや、なぜそれを残さなかったのかにも学びがある

コーディングにおいてはもっとわかりやすいですが、「書かなかった理由 = 選ばなかった理由」であり、できる人のコードの書く瞬間が一番勉強になります。
現に自分であれば書いたであろう1行を書かないわけですから、その意図や理由は非常に勉強になります。

技術選定にしろ、コードを書くことにろ、結局は「課題解決」の手段として捉えればできる人の意思決定を真似るには できる人が選ばないことを自分も選ばなければいい ということに落ち着くのではないかと考えています。

ある特定の問題解決を使用する際に、いくつかソリューションの候補はあげますが、いざ決める時にその人の「思考」が詰まっています。
その思考のことをノウハウというのではないかと考えてるようになりました。

何かの意思決定をすることは することを決める ことですが、これは しないことを決める と同義です。
しかしながら、することを決める ノウハウは様々な場で知見が共有される一方で、しないことを決める ノウハウは語られることは少なく、そもそも語られなかったりして、「なぜ選ばなかったのか?」「なぜしなかったのか?」ということはあまり知見として広まっていないように感じています。

意思決定の瞬間の思考を深掘りするときに、どうして選ばなかったのかをしっかり説明できるようになりたいし、意思決定する際には「しない」理由にも目を向けると、自身の価値判断の手数が増えていき、武器が増えていくように感じます。

たくさんの選ばない理由に触れ、吸収し続けることの大事さを最近感じる出来事があったので、備忘録としてまとめて見ました。

おわり

depでginを入れる

やったこと

depをつかってginをinstallして動かすまで。

depのinstall方法

以下を見て下さい。

ema-hiro.hatenablog.com

depでginを入れる

depでginをinstallします。

# projectのROOTにいるとする。
$ cd ./src/{project_name}
$ dep init
$ dep ensure -add github.com/gin-gonic/gin

終わり...

のはずだった。

しかし、goファイルがないよ!!! というエラーが出る事が分かったので、src/{project_name} 直下に main.go を置いてpackage mainを書いて再度 dep ensure -add をすることでginをDLする事ができます。

go ✕ ajaxを書いてみた

サマリ

goで簡易的なajax通信するアプリを作ったのでそのメモ

構成

環境

ディレクトリ構成

- src
  - app
    - main.go
    - handler
      - handler.go
    - render
      - render.go
- templates
  - index.tmpl

sampleコード

main.go

var port = "8080"
func main() {
    router := mux.NewRouter()
    router.HandleFunc("/", handler.Top).Methods("GET")
    {
        router.HandleFunc("/sample", handler.SamplePost).Methods("POST")
    }
    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), router); err != nil {
        log.Fatalf("err: %v", err)
    }
}

handler/handler.go

func SamplePost(w http.ResponseWriter, r *http.Request) {
    // postのリクエストを処理する
  client := http.Client{}
  req, err := http.NewRequest("POST", "http://sample.com/create", nil)
    if err != nil {
        log.Fatalf("build request error. err: %v", err)
    }
    // form をparseする
    if err := r.ParseForm(); err != nil {
        log.Fatalf("parse form error. err: %v", err)
    }

    // 何かしらrequestのbodyにparseしたURLのパラメータを入れ込む処理か何かが入る
  /*
    何かしらの処理
  */
  
    resp, err := client.Do(req)
    if resp.Status != "200 OK" {
        log.Fatalf("http request failed. code: %v", resp.Status)
    }
  json.NewEncoder(w).Encode(&resp)
}

index.tmpl のjsの部分

var url = $("form").attr("action");
var p = $("form").serialize()
$.ajax({
  url: url,
  type: "post",
  data: p,
}).done(function (data) {
  res = JSON.parse(data);
  // responseで返ってきたjsonを◯◯する
}).fail(function (err) {
  console.log(err)
});

ハマったところ

requestはそのままparseする

簡易的なajaxなので今回はjqueryを使ってさらっと書いてますが、goでPOSTリクエストを受けて、serializedされたパラメータを url.Values 型として扱うためには、ポインタとして受け渡される http.Request をそのままparseします。

// form をparseする
    if err := r.ParseForm(); err != nil {
        log.Fatalf("parse form error. err: %v", err)
    }

http.Request の持つ関数の中に PostFrom というのがありますが、これは新しく空の url.Values を作るだけで、フロントからリクエストされたrequestをparseしてくれるわけではないです。

直感的な名前がついていたために、間違って使っていて、どうしてもrequestを一度でparseできないと悩んでしまっていました。

まとめ

validationとか一切考えない超簡易的なajaxを書いてみましたが、標準のライブラリしか使っていないのに、案外簡単に書けました。

GoLandの設定をremoteで管理する

Golandの設定をremoteで管理したかったので、その設定方法をメモとして書いておきます。

背景

PC変えたりすると使っていたPCの設定が全て初期化されて1から作り直すのめんどくさいです。
PCやeditorくらいであればもしかしたら、設定ファイルをgithub等にあげて clone してくれば設定完了みたいなことはしていましたが、jetbrains系のIDEの設定までremoteで管理していなかったので、この機会にGoLandを使ってIDEのremoteめsettingをsyncの方法を記載します。

ツール

  • Settings Repository (Browse Jetbrains Plugin)
  • GithubのAccessToken (Settings -> Developer Applications -> Personal access token)

の2点を利用します。

手順

Settings Repository をDLしてきます。
Plugin -> Browse Jetbrains Plugin から Settings Repository をinstallします。

f:id:ema_hiro:20171125031928p:plain

次にGithubのAccessTokenを取得します。
個人のGithubのアカウントを作成しておき
* Settings -> Developer applications -> Personal access token に遷移してGoLand用にアクセストークンを取得します。

GoLandに戻ってきたら Settings Repository を起動します。
GithubリポジトリのURLとaccess token の入力を求められるので、上記手順で取得したgithubaccess tokenを入力します。

適当なプロジェクト開いて
「File」 -> 「Settings Repository」 に遷移します。

f:id:ema_hiro:20171125032249p:plain

この時に以下のようなwindowが表示されるので Override local でローカルを上書きします。

f:id:ema_hiro:20171125032420p:plain

※ どうも一度目は Override local をしなければならないみたいです。

あとはaccess tokenによってremoteで接続すべきGithubのレポジトリも繋がっているので、IDEを終了したタイミングでgithub上に作成したremoteのリポジトリと設定がsyncされます。
これで他端末でも同じ設定が使えます。

refs

Sharing Your IDE Settings - Help | IntelliJ IDEA

qiita.com

QiitaのAPIで遊ぶ

サマリ

APIで遊びながらgoの学習をするシリーズ第二弾で、Qiitaで記事を検索するクライアント goota をgoで書きました。

コードはこちら

github.com

demo

f:id:ema_hiro:20171124023348g:plain

ざっくり仕様

Requirement

  • tagを指定できる。
  • tagはカンマ区切りでOR条件で検索出来る。
  • ストック数が100以上の記事にする。
  • 簡易的なAjaxを使ったSPAとする

はまったところ

Qiitaの仕様が変わって、従来のStocksがQIitaでは「いいね」を指していたので、最初クエリを組み立てる時に likes_count を設定してやろうとしても一件も記事が返ってこなくて困ってた。

refs

qiita API Document

qiita.com

「Qiita APIで投稿一覧を取得するときに、検索クエリをORでつなぐ時の注意点」

qiita.com

githubのSearchAPIで遊んだ話

githubのsearchAPIを簡単にラップしたGUI作りました。

f:id:ema_hiro:20171119031801g:plain

久しぶりにjqueryとか触ったらすごい懐かしい匂いがして色々つまりました。
request処理とかしててハマったところがあるので別でエントリでまとめようと思います。

コードは以下 github.com

refs: はまったところは以下でまとめてみた

ema-hiro.hatenablog.com