My Note

自己理解のためのブログ

個人開発中 ( 金融資産管理サイト) のここまでのまとめ ( Go Echo on GAE + PlanetScale(DB) + Upstash(Redis) )

はじめに

個人開発で取り組んでいる内容について一区切りついたので一度ここまでをまとめてみようと思います。 開発内容は自分の投資情報を管理して可視化するためのサイト構築。 以前に作成した下記の記事で データ保存に GAS + SpreadSheet と可視化に Google Data Portalを利用していました。 その SpreadSheetのデータ保存を MySQL + Redisに、可視化に Go + Templateレンダリング + go-echarts に置き換えるイメージで実装しています。

yhidetoshi.hatenablog.com

Google CloudやSaaSを利用していますが、ランニングコストをかけずに運用するために基本的に無料枠、無料プランを活用しています。

システム

システム構成図

■ 技術スタック

  • 言語
    • Go1.6 html css js
  • ORM
    • gorm
  • サーバーサイド
    • Echo
  • 実行環境
    • 開発 (Docker)
      • Go + MySQLコンテナ + Redisコンテナ
    • リリース
  • データベース ( SaaS )
  • CI / CD
    • Github Actions
      • go test & lint
      • app engine deploy
  • バッチ実行クライアント
  • モニタリング
    • Cloud logging + Google Monitoring
      • ログ監視 + メール通知
    • Mackerel
      • google cloudインテグレーション
        • GAEのリソース監視

システムの概要

ディレクトリ構成

構成

├── 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のサービスで提供されており利用時にリージョンを選択することができました。 操作に関しては管理コンソール、もしくは pscaleCLIも用意されています。管理コンソールからSQLも発行可能です。

planetscale.com

  • 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上で提供されています。

upstash.com

Redisは、セッション管理とバッチ処理の結果を保存して利用。 詳しくは以前書いた記事で。

yhidetoshi.hatenablog.com

■ 接続情報について

データベースを作成すると接続情報が発行されます。

  • 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ブランチにマージして自動デプロイする運用にしています。

yhidetoshi.hatenablog.com

yhidetoshi.hatenablog.com

yhidetoshi.hatenablog.com

バッチ処理の実行について

評価額と損益額はバッチ処理で更新しています。1日1回実行しています。 バッチ実行するためのAPIを叩きます。 今回用意したパスは /api/exec_crypto_bat

GASに関しては、以前の記事を参照。

yhidetoshi.hatenablog.com

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 利用しています。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

ORマッパーについて

ORマッパーに gorm を利用しています。詳細は下記の記事にまとめています。 yhidetoshi.hatenablog.com yhidetoshi.hatenablog.com

CORSについて

サーバサイドのEchoにCORSを設定しています。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

ヘルスチェックについて

今回実装したヘルスチェックについては書きの記事にまとめています。

yhidetoshi.hatenablog.com

ページの作成について

"Template Rendering" を利用してサーバーサイドからデータを渡して表示させました。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

グラフの描画について

評価額と損益額の推移をグラフ化するために go-echarts を利用しました。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

金額表示のカスタム

3桁区切りにカンマを入れないと表示が見づらいのでスター数も多く下記のライブラリを利用しました。 Goで自前で書くとコード量が多くなるので。

github.com

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でかつ費用をかけずに利用できるのは本当にありがたい...。