個人開発中 ( 金融資産管理サイト) のここまでのまとめ ( Go Echo on GAE + PlanetScale(DB) + Upstash(Redis) )
- はじめに
- システム
- データベース
- CI / CD について
- バッチ処理の実行について
- ログインパスワードの暗号化について
- ORマッパーについて
- CORSについて
- ヘルスチェックについて
- ページの作成について
- グラフの描画について
- 金額表示のカスタム
- モニタリングについて
- さいごに
はじめに
個人開発で取り組んでいる内容について一区切りついたので一度ここまでをまとめてみようと思います。 開発内容は自分の投資情報を管理して可視化するためのサイト構築。 以前に作成した下記の記事で データ保存に GAS + SpreadSheet と可視化に Google Data Portalを利用していました。 その SpreadSheetのデータ保存を MySQL + Redisに、可視化に Go + Templateレンダリング + go-echarts に置き換えるイメージで実装しています。
Google CloudやSaaSを利用していますが、ランニングコストをかけずに運用するために基本的に無料枠、無料プランを活用しています。
システム
システム構成図
■ 技術スタック
- 言語
- Go1.6 html css js
- ORM
- gorm
- サーバーサイド
- Echo
- 実行環境
- 開発 (Docker)
- Go + MySQLコンテナ + Redisコンテナ
- リリース
- Google App Engine (Go) + PlanetScale (MySQL) + Upstash (Redis)
- 開発 (Docker)
- データベース ( SaaS )
- CI / CD
- Github Actions
- go test & lint
- app engine deploy
- Github Actions
- バッチ実行クライアント
- Google App Script
- モニタリング
システムの概要
■ ディレクトリ構成
構成
├── README.md ├── TEST_Client │ └── cors │ ├── index.php │ └── script.js ├── api │ ├── crypto │ │ ├── coincheck │ │ │ └── coincheck.go │ │ ├── crypto_bat │ │ │ └── bat.go │ │ └── gmo │ │ └── gmo.go │ ├── healthcheck │ │ ├── healthcheck.go │ │ └── healthcheck_test.go │ ├── metal │ │ └── metal.go │ └── stock │ ├── kakakucom │ │ └── kakakucom.go │ └── stock_bat │ └── bat.go ├── app.yaml ├── conf │ └── config.go ├── docker │ ├── mysql │ │ ├── Dockerfile │ │ ├── data │ └── redis │ └── data ├── docker-compose.yml ├── go.mod ├── go.sum ├── handler │ ├── auth │ │ └── auth.go │ ├── crypto │ │ └── crypto.go │ ├── graph │ │ ├── crypto.go │ │ └── stock.go │ ├── hash │ │ └── hash.go │ ├── stock │ │ └── stock.go │ └── top.go ├── main.go ├── model │ ├── base.go │ ├── crypto │ │ ├── daily_rate │ │ │ └── daily_rate.go │ │ ├── exchange │ │ │ └── exchange.go │ │ ├── exchange_result │ │ │ └── result.go │ │ ├── token │ │ │ └── token.go │ │ ├── token_result │ │ │ └── result.go │ │ └── trade_history │ │ └── trade_history.go │ ├── date │ │ └── date.go │ ├── stock │ │ ├── daily_price │ │ │ └── daily_price.go │ │ ├── hold │ │ │ └── hold.go │ │ ├── investment_trust │ │ │ └── investment_trust.go │ │ ├── investment_trust_result │ │ │ └── investment_trust_result.go │ │ ├── isin_code │ │ │ └── isin_code.go │ │ └── securities_company │ │ └── securities_company.go │ └── user │ └── user.go ├── public │ └── assets │ └── css │ ├── crypto_register.css │ ├── crypto_summary.css │ ├── crypto_trade_history.css │ ├── login.css │ ├── mypage.css │ ├── signup.css │ └── stock_register.css ├── script │ └── setup_local_env.sh ├── secret.yaml ├── staticcheck.conf └── view ├── crypto_register.html ├── crypto_summary.html ├── crypto_trade_history.html ├── login.html ├── mypage.html ├── password_update.html ├── signup.html ├── stock_hold.html ├── stock_register.html ├── stock_summary.html └── top.html
機能
- Sign Up
- Login / Logout
- Password更新
- セッション管理
- バッチ処理
- 外部APIコールして価格取得
- 評価額、損益額の更新
- 取引データ入力ページ
- 取引履歴確認ページ
- 取引状況のサマリーページ(評価額と損益額)
各ページ画面のスクリーンショット
■ SignUp ( localhost:8080/signup )
■ Login ( localhost:8080/login )
■ MyPage(メニュー) ( localhost:8080/mypage )
■ Cryptoの取引データ入力 ( localhost:8080/crypto/trade )
■ Cryptoの取引履歴 ( localhost:8080/crypto/trade_history?page=1 )
■ Cryptoのサマリーページ ( localhost:8080/crypto/summary )
■ Crypto 表示するグラフの選択して "表示" ボタンをクリック(トークン別か取引所別か)
■ Crypto 全期間のトークン評価額のグラフ ( localhost:8080/crypto/summary/token/btc )
■ Crypto 全期間のトークン評価額のグラフ ( localhost:8080/crypto/summary/exchange/gmo )
(他にも違う期間でのグラフを用意してます)
■ パスワード更新 ( localhost:8080/mypage/password_update )
■ 投資信託の情報更新 ( localhost:8080/stock/trade )
■ 投資信託とコモディティの保有情報 ( localhost:8080/stock/hold )
■ 投資信託とコモディティの評価額、損益額情報 ( localhost:8080/stock/summary )
■ 投資信託を選択したときのチャート(評価額と損益額) ( localhost:8080/stock/summary/investment_trust/${NAME_OF_INVESTMENTTRUST} )
テーブル構成
MySQL WorkBench のDatabase → Reverse Engineer
から図を作成。
以下のツールを利用してGithubで資料化するのもいいと思います。(生成された画像が svgではてブロが非対応形式だったので画像は WorkBenchで作成しました。) github.com
(テーブル設計も勉強しつつ取り組んだのでもっといい方法があると思います。ご参考までに)
データベース
PlanetScale( DB )
無料でデータベースを利用する手段を探した結果、"PlanetScale" というサービスを見つけました。 見つからない場合は Google Compute Engineの無料インスタンスに MySQLをローカルにインストールするなど考えていましたが、アクセスや運用が大変そうだったのでできれば避けたかったのでとても助かりました。(後ほど記載する Upstash (Redis) も同様です)
データベースはAWSのサービスで提供されており利用時にリージョンを選択することができました。
操作に関しては管理コンソール、もしくは pscale
のCLIも用意されています。管理コンソールからSQLも発行可能です。
Price
- PlanetScale Pricing
- 2022/05 時点ではストレージが10GBまで無料でしたが、記事作成した時点では5GBまでのようです。(要確認)
- 今回の個人開発で利用するに無料プランで十分利用できています。
pscaleコマンドの例
pscale auth login # 認証 pscale database dump <db_name> <brach_name> # dump取得
■ 接続情報について
データベースを作成すると、接続情報が発行されます。 EchoでのDBの情報は以下のように設定しました。
- secret.yaml(一部抜粋)
DB_USER: "xxx" DB_NAME: "xxx" DB_PORT: "3306" DB_HOST: "xxx.ap-northeast-2.psdb.cloud" DB_PASS: "pscale_pw_xxx" DB_OPTION: "?tls=true&parseTime=true"
- conf/config.go(一部抜粋)
var ( DB_USER = os.Getenv("DB_USER") DB_NAME = os.Getenv("DB_NAME") DB_PORT = os.Getenv("DB_PORT") DB_HOST = os.Getenv("DB_HOST") DB_PASS = os.Getenv("DB_PASS") DB_OPTION = os.Getenv("DB_OPTION") DB_ENDPOINT = DB_USER + ":" + DB_PASS + "@tcp(" + DB_HOST + ":" + DB_PORT + ")/" + DB_NAME + DB_OPTION )
- model/base.go(一部抜粋)
var db *gorm.DB func init() { var err error dsn := conf.DB_ENDPOINT db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
Upstatsh (Redis)
こちらもPlanetScaleと同様でSaaSとして無料プランが用意されており利用しました。詳細は下記の公式サイト。 また、環境はAWS上で提供されています。
Redisは、セッション管理とバッチ処理の結果を保存して利用。 詳しくは以前書いた記事で。
■ 接続情報について
データベースを作成すると接続情報が発行されます。
- secret.yaml(一部抜粋)
REDIS_HOST: "xxx" REDIS_PORT: "33046" REDIS_PASS: "xxx"
- conf/conf.go
var ( REDIS_HOST = os.Getenv("REDIS_HOST") REDIS_PORT = os.Getenv("REDIS_PORT") REDIS_PASS = os.Getenv("REDIS_PASS") )
- redis接続には
github.com/go-redis/redis/v8
を利用した場合
client := redis.NewClient(&redis.Options{
Addr: conf.REDIS_HOST + ":" + conf.REDIS_PORT,
Password: conf.REDIS_PASS,
})
CI / CD について
CI/CDにはGithub Actionsを利用しています。こちらも無料枠が用意されているので料金コストをかけずに利用しています。 主に、go test & lint、Google App Engineにデプロイ。 ブランチは mainブランチにマージして、GAEにデプロイするときは releaseブランチにマージして自動デプロイする運用にしています。
バッチ処理の実行について
評価額と損益額はバッチ処理で更新しています。1日1回実行しています。
バッチ実行するためのAPIを叩きます。
今回用意したパスは /api/exec_crypto_bat
GASに関しては、以前の記事を参照。
function execBatGAEWebAppInvestment() { const gaeEndpoint = PropertiesService.getScriptProperties().getProperty("GAE_INVESTMENT_ENDPOINT") // GAEのエンドポイント const requestBatCryptoPath = "/api/exec_crypto_bat" // バッチ実行APIパス const basicAuthId = PropertiesService.getScriptProperties().getProperty("ID") // Basic認証のID(セットプロパティから取得) const basicAuthPass = PropertiesService.getScriptProperties().getProperty("PASS") // Basic認証のPW(セットプロパティから取得) // Basic認証 let urlOptions = { method: "POST", headers: { "Authorization": "Basic " + Utilities.base64Encode(basicAuthId + ":" + basicAuthPass) } } execBatCrypto(gaeEndpoint, requestBatCryptoPath, urlOptions) } function execBatCrypto(gaeEndpoint, requestPath, urlOptions) { let url = gaeEndpoint + requestPath UrlFetchApp.fetch(url, urlOptions); }
ログインパスワードの暗号化について
ログインパスワードの暗号化に bcrypt
利用しています。詳細は下記の記事にまとめています。
ORマッパーについて
ORマッパーに gorm
を利用しています。詳細は下記の記事にまとめています。
yhidetoshi.hatenablog.com
yhidetoshi.hatenablog.com
CORSについて
サーバサイドのEchoにCORSを設定しています。詳細は下記の記事にまとめています。
ヘルスチェックについて
今回実装したヘルスチェックについては書きの記事にまとめています。
ページの作成について
"Template Rendering" を利用してサーバーサイドからデータを渡して表示させました。詳細は下記の記事にまとめています。
グラフの描画について
評価額と損益額の推移をグラフ化するために go-echarts
を利用しました。詳細は下記の記事にまとめています。
金額表示のカスタム
3桁区切りにカンマを入れないと表示が見づらいのでスター数も多く下記のライブラリを利用しました。 Goで自前で書くとコード量が多くなるので。
GitHub - dustin/go-humanize: Go Humans! (formatters for units to human friendly sizes)
モニタリングについて
バッチ実行のログ監視
Google App Engineのログは Cloud loggingに送られます。
今回は特定のログを抽出するために以下のクエリにしました。バッチの結果のステータスコードが 200 以外だった場合にメール通知します。
resource.type="gae_app" jsonPayload.host="xxx" jsonPayload.uri="/api/exec_crypto_bat" jsonPayload.status!=200
以前は Cloud loggingと Cloud Pub/Sub と Cloud Functions も用意する必要があったんですが、今回設定するにあたり調べたら Cloud Monitoringだけで設定が可能になっていました。かなり楽になりました。
今回、設定するにあたり下記の記事の手順を参考にさせていただきました。
Google Cloud ログベースのアラート機能を使って エラーログが出たら Slack に通知する
また、バッチの実行クライアントはGoogle App Scriptなので、クライアントの実行エラーもメールが届くようにしています。
Mackerel (Google Cloud インテグレーション)
Mackerelに Google Cloud インテグレーション機能でモニタリングしています。取得できるメトリクスや設定方法は 公式ドキュメントに記載されています。
Google Cloudインテグレーション - App Engine - Mackerel ヘルプ
(ありがたい事にMackerelアンバサダーの特典で無料で利用させていただいています。)
さいごに
今回の個人開発は趣味から派性してあったらいいなぁという気持ちから作り始めました。普段はインフラエンジニア/SREの担当なので個人的に サーバサイドやフロント側をいじれるのは勉強になり面白いです。 まだまだ追加したい機能があるので適宜リファクタリングしつつ徐々に開発を続けていきたいと思います。 GAEやMySQL、RedisがSaaSでかつ費用をかけずに利用できるのは本当にありがたい...。