DDD を手になじませるため、Go言語 & DDD習作を作りたかった
まず、そもそもやりたいこととして、Fitbit の睡眠情報と運動の情報を Google Calendar に同期させたかった。
そのためには、
- Fitbit の認証を通す
- Fitbit から Sleep, アクティビティ のデータを取得
- Google Calendar の認証を通す
- Google Calnedar に Sleep, アクティビティ を予定として追加
というプロセスが必要だった。これを実はちょうど 1年前に Python で作っていた。
Go-zen-chu/fitbit2gcal: Get data from Fitbit and register them to Google Calendar. That’s it.
ただ ↑は localhost で立てた API で Oauth2.0 の authorization_code を取得して、access token まで取った上で、それを環境変数に設定し、Google Calendar へイベントを追加する仕組みになっていた。
つまり、認証を自分で通してから起動し、過去 2週間分の予定を登録するような面倒くさい仕組みになっていた。
その結果、お察しの通り、面倒くさくて今は動かせていない。
また、Google App Engine (GAE) で動かそうという野望があったのだが、上記のとおり、access token を環境変数に差し込む仕様のせいで、移行ができなかった。
GAE の App として作り、自動的に Fitbit からデータを取得したい
どうせ作り直すなら、Go言語、さらに最近業務で意識している DDD な作りでやったるで! というのがこの記事の内容。
プロジェクトは、Go-zen-chu/gae-fitbit-go: GAE App to fetch data from fitbit in golang
フローチャートを作る
まずプログラムを書く上で、これまでの反省として「書いてるうちに見えてくるものがあるだろう」スタイルを止めたかった。
なぜかというと、実際の業務では複数人で開発するので、例えスクラムだとしてもあらかじめ設計が見えていたほうが圧倒的に手戻りが少ないからだ。
また、「書いているうちに見えてくる」スタイルだと、いくらか工数をかけて良い設計が見えてきたとしても、他の人もすでに着手しているため、「もうそのままでいいじゃん…」となりがち。そして、これがスパゲッティなプロダクトの生まれる瞬間になるのだ。
というわけで、まず自分がどのように作るのかを設計した。
もちろん、テーマは plantuml の style をモダンにする – Think Abstract
Domain を考える(ドメイン駆動設計のポイント)
DDD のもっとも重要なところは、独立した Domain が何かを考えることだ。
今回の例で言えば、
- Fitbit の認証を扱う機能
- Fitbit からデータを取得し、Google Calendar へ登録する機能(コアロジック)
- API のヘルスチェック用の機能
で分けてみた。1 と 2 で Fitbit を扱うため、同じにすることも考えたのだが、1 は Oauth2.0 の認証トークンを取得するだけ、 2 はコアロジックという風に考えれば、独立できそうだと考えた。
Domain 間の依存は基本的にない、あるいは非常に疎でなければならない。 そもそもスパゲッティコードは、「プログラム内の依存関係が複雑すぎて、修正が容易にくわえられないもの」だ。DDD は 依存関係に法則を与えることで複雑なシステムをわかりやすく作ろうという設計 なのだ。
であれば、2 のコアロジックも Fitbit からデータを取得するのと、Google Calendar へ登録することを分けたほうが良いのでは? と考えるだろう。
個人的にそれはケース・バイ・ケースで、今回はこのコアロジックがシンプルなため、一緒にした。
1, 2 をわけたことで、Store に工夫が必要になる。 1 では Fitbit の認証コードやトークンを R/W できるようにしたが、2 はトークンの読み取りだけで十分のため、似たようなことを行うもののあえて共通化はしなかった。
わかりやすい依存関係 > 共通化 なのだ。スパゲッティコードも変に共通化されたり、使い回されたりしていることが起因であることが多い。(DRY も万能ではないのだ)
Fitbit Web API を Go言語で叩く
DRY に従って、web client ないかなーと探していたが、トップに出てきたのが、simonacca/go-fitbit: A client library for accessing the Fitbit Web API で、機能が足りなさそうだと思った。
【なつやすみのしゅくだい】go言語でfitbitAPIを叩いて睡眠情報を取得する。 - y.takky てくめもも拝見したが、こちらもまだ途上という形であり、Oauth2.0 の authorization_code grant flow を理解する意味でも、1からつくることにした。
Fitbit API の Status Page
ありがたいことに、そもそも Fitbit API が生きているかどうかは、Fitbit Status で確認ができる(これどう見ても、Cachet だなぁ)
API は稼働しているようなので、ちゃんとリクエストが送れたら問題なさそうだ。
Fitbit web API の認証ではまった
authorization_code grant flow は一件難しそうだが、そこまで難しい仕組みではなく、
- Fitbit の認証ページ (<www.fitbit.com>) へリダイレクト
- ユーザが Fitbit のページで承認
- Fitbit からのリダイレクトで、自分の API に authorization_code が送られてくる
- authorization_code を使って、api.fitbit.com に access_token をリクエスト
- api.fitbit.com から access_token を受け取り、それをもとに sleep, activity を取得する
という流れになっている(上記フローチャートでもそのようになっている)
はまったポイント
認証ページは <www.fitbit.com> だが、api は api.fitbit.com だった
Fitbit の URL を共通化して api.fitbit.com にしていたため、Bad Request となった。
json.Unmarshal
初歩的だが、
var ft *FitbitTokens
err = json.Unmarshal(bd, ft)
return ft, nil
-> Error while decoding token: : json: Unmarshal(nil *fitbitauth.FitbitTokens)
と Unmarshal に失敗していた。理由は簡単で FitbitTokens のポインタへ Unmarshal しようとしているからだった。 そのため、下記が正しい。
var ft FitbitTokens
err = json.Unmarshal(bd, &ft)
return &ft, nil
http request で GET ではなく Get としていた
どこかのコードを参考にしたとき、http.NewRequest(“Get”,…) となっていて、
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
というのが返ってきた。API も、リクエストパラメータも、access_token もあっていたため、まさか GET メソッドが Case Sensitive だと思わず、しばらく悩んだ。(´・ω・`)
Google Calendar の API を Go言語で叩く
TO BE DONE…