yn2011's blog

技術メモ

HerokuでDocker, Go, Postgresを使ってAPIサーバを動かしてみた感想

環境

モチベーション

  • データのCRUDを伴うWebAPI(とDB)をサクッと作って公開したい

どうやったか

  • ローカルはAPIとDB共にDockerで環境構築
    • APIはそのDockerfileを使ってHerokuにデプロイ
    • DBはHeroku addonsのPostgresを使用
  • APIの実装はgin, gormを使って楽をした
  • スキーマのmigrationはgoose(下の記事で使われてたのでそのまま使った)

できたもの

https://infinite-hollows-34092.herokuapp.com/api/ping

GETリクエストすると現在日時を格納する。これまでに格納された現在日時を全て返す。

github.com

手順

以下の手順に従って進めた。上記のコードはほぼ以下の記事のコードの写経に近い。フロントエンドがないのとgormを使っている点が違うだけ。

以下、感想を書きます。

Heroku addonsのPostgresについて

今まで使ったことなかったけど、コマンド1つでクラウド上にDBを作成できてここまで色々できるの凄いなと思った。

Dataclips

data.herokuのDataclipsを使うとブラウザからPostgresを参照できる。参照系のクエリを書くのでViewみたいな感じ。 このViewに対するURLを発行することもできて外部連携も可能な模様。

f:id:pokuwagata:20200222155840p:plain

pgsql

ターミナルからpg:psqlで接続することも可能。

❯ heroku pg:psql
--> Connecting to postgresql-rectangular-83658
psql (12.1)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

infinite-hollows-34092::DATABASE=> delete from ping_timestamp where id = 4;
DELETE 1
infinite-hollows-34092::DATABASE=> select * from ping_timestamp;
 id |           occurred
----+-------------------------------
  1 | 2020-02-22 06:06:15.302206+00
  2 | 2020-02-22 06:06:26.004438+00
  3 | 2020-02-22 06:06:33.051055+00
(3 rows)

infinite-hollows-34092::DATABASE=> \q

Dockerを利用したHeroku デプロイについて

Dockerfileを使ってHerokuにデプロイするためにheroku.ymlを書く。例えば今回は以下になる。

build:
  docker:
    web: Dockerfile
    worker:
      dockerfile: Dockerfile
      target: builder    
release:
  image: worker
  command:
    - ./migrate.sh

ちょっとイメージしにくかったのがreleaseフェーズで使用されるworkerの部分

  • このDockerfileではmulti stage buildをしていて、Goのモジュールのビルドと実行を分離している
  • releaseフェーズではPostgresに対してmigrationを実行するだけだが、その実行環境をworkerとしてbuildフェーズで事前にビルドしている
    • なので同一のDockerfileを元に2つのイメージを作成している。webの方はGoのビルドと実行までで、workerはGoのビルドまで。

これは実際に$ git push heroku masterしてHerokuが出力してくれるログを眺めると何をしているのかが分かって良かった

その他

  • Netlify, Heroku, Firebase等、遊びで何かを作って公開するのが楽な時代になった
  • 業務のサービス開発にいつも使えるとは限らないけど、選択肢として持っておくべきと思う
    • 仮説検証的・速度重視の開発や、パフォーマンスを求められない単機能なWebAPIの開発など?

docker rmiで image is referenced in multiple repositoriesが発生した

環境

  • macOS 10.14.6
  • Docker version 19.03.5, build 633a0ea

Dockerの掃除をしていたら

  • docker images を全削除するを参考に、イメージとコンテナの掃除をしていた
  • その中になぜか削除できないイメージがいた
~/.ghq/github.com/pokuwagata/go-gin-gorm-heroku-example master* 11s
❯ docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE
portfolio-go-mysql-docker-webapp_nginx   latest              8a2fb25a19f5        10 months ago       16MB
nginx                                    1.14-alpine         8a2fb25a19f5        10 months ago       16MB

~/.ghq/github.com/pokuwagata/go-gin-gorm-heroku-example master*
❯ docker images -aq | xargs docker rmi
Error response from daemon: conflict: unable to delete 8a2fb25a19f5 (must be forced) - image is referenced in multiple repositories
Error response from daemon: conflict: unable to delete 8a2fb25a19f5 (must be forced) - image is referenced in multiple repositories

An image will be listed more than once if it has multiple repository names or tags. This single image (identifiable by its matching IMAGE ID) uses up the SIZE listed only once.

docker images | Docker Documentation

  • そういうわけで、idではなく、リポジトリ(とタグ)を指定してdocker rmiすれば削除できる
❯ docker rmi portfolio-go-mysql-docker-webapp_nginx

結果

スッキリ

~/.ghq/github.com/pokuwagata/go-gin-gorm-heroku-example master*
❯ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

~/.ghq/github.com/pokuwagata/go-gin-gorm-heroku-example master*
❯ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

定期的に掃除していきましょう

urfave / cliを使ってGoでCLIアプリを作ってみた

環境

  • macOS 10.14.6
  • go 1.12
  • urfave / cli v2.1.0

作ったもの

github.com

f:id:pokuwagata:20200208222215g:plain

何だこれは

苺ましまろという作品に登場するキャラクターの台詞を出力するだけのアプリ。 台詞は3パターンしか登録していない。アナちゃんと茉莉ちゃんは未対応。

モチベーション

感想

  • Go言語のcliアプリ作成支援系のライブラリとしてはspf13/cobraもあり、あまり大きな違いはなさそうなので名前でcliの方を選んだ(コブラは何か怖い)
    • そもそもGoでcliアプリ作るなら、ライブラリ使わなくても十分実装できるだろうなという思いもあったが、とりあえず最初はライブラリ使ってみた
  • Github Actionsを初めて使った。tagをpushするだけで各環境向けのビルドが完了してrelesesページで公開されるの凄い
  • Go言語で書いているとGithubからgo getでインストール完了するので楽。皆、ichigo-cliをgo getしましょう。

ハマりどころ

  • Github Actionsとローカルでgo.sumに差異が発生してビルドに失敗した。原因はローカルがGo 1.12なのに対しActionsは1.13だったから。 ローカルのバージョン上げないと...と思いつつ一旦は1.12に合わせて、$ go mod tidyして解決した。

  • 台詞をランダムに表示するために、math.randを使用して乱数を取得しようとしたら乱数にならなくて困った。 原因はseedの初期化をしていないかららしく、そういうものなのか...と思った。現在時刻をseedに使うようにしたら確かに乱数になった。

  • 2ヶ月ぐらいGo書いていなかったので色々忘れていたけど、「たしかこういう構文があった」とか「名前は忘れたがこんな関数が標準パッケージにあったはずだ」みたいな感じで気づくことは出来ているので、ある時期に集中的に書いていればしばらく書いていなくても何とかはなるなあとも思った。あと、やっぱり言語の色々がシンプルなのは人の記憶に優しいのかもなあとも思ったり。

Go言語、沢山書いていきたいですね。

vim-lspを使用してtsxファイルに対してtypescript-language-serverを動かす

環境

  • macOS 10.14.6
  • VIM - Vi IMproved 8.2
  • prabirshrestha/vim-lsp v0.1.0
  • mattn / vim-lsp-settings v0.0.1

方法

前提:vim-lspはインストール済み

wikiの手順と同じだが、whitelistの設定が異なる。

  • LSPをインストール
$ npm install -g typescript typescript-language-server
  • 下記を.vimrcファイルに追加する
if executable('typescript-language-server')
  au User lsp_setup call lsp#register_server({
        \ 'name': 'typescript-language-server',
        \ 'cmd': {server_info->[&shell, &shellcmdflag, 'typescript-language-server --stdio']},
        \ 'root_uri':{server_info->lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'tsconfig.json'))},
        \ 'whitelist': ['typescript', 'typescriptreact'],
        \ })
endif
  • デフォルトだとファイルタイプtypescriptのみが有効なので.tsxファイルを開いてもLSPが動作しない

  • .tsxファイルを開いて:LspStatusする

    • typescript-language-server: running と表示されればOK

vim-lsp-settings

(上記のvimrcを書かなくて済むので)vim-lsp-settingsを使いたかったが現状ではうまく動作しなかった

  • ファイルタイプtypescriptreactに対して:LspInstallServerはサポートされていない(README.mdの表にない)
  • しかしtypescript-language-serverを使うことは変わりないので、.tsファイルを開いて:LspInstallServerしてLSPをインストール
  • let g:lsp_settings = {'typescript-language-server':{'whitelist': ['typescript', 'typescriptreact']}}として上書きしたが、
    • .tsファイルを開いた後に.tsxファイルを開くと動作する
    • .tsxファイルを始めに開くと動作しない

動作することはするけど不完全だったので一応issueを作成してみた(英文難しい...)

TypeScript, Go, Docker Composeに入門するために約半年間かけてWebサービスを構築した話

この記事はチームスピリット アドベントカレンダー2019の22日目の記事です。

今年(2019年)の7月から12月にかけて約半年間、プライベートでコツコツ開発していたWebサービスが一応形になった。技術的な内容は別の記事で書く予定なので、今回は半年間を通じて感じられた個人開発という営み自体の難しさや継続のコツについて書いてみる。

作ったもの

これ何

  • ブログのような何か
    • 認証認可、記事一覧、投稿、削除ができる

構成

https://raw.githubusercontent.com/pokuwagata/portfolio-go-mysql-docker-webapp/master/docs/imgs/system-overview.png

モチベーション

  • 0からWebサービスを構築してみたい
  • (業務ではSalesforceを使っているので)たまにはSalesforceから離れたい
  • TypeScriptやGoで、それなりの量のコードを書きたい

なお、ブログサービスは題材として良さそうだっただけで、サービスとして何かやりたいことがあったわけではない...

所感

実際にやってみて感じたこと

時間確保の難しさ

  • 平日の日中は仕事があるので、平日の朝・夜か休日に開発
    • とにかく15分でもいいから毎日確実に作業することが凄く大事
      • 休日だけ開発をすると、始める際に「先週何をしていたか思い出す」負担が大きくて効率が悪い
    • 意外と良かったのが昼休みに開発することで、仕事モードのまま開発を始めると凄く集中力が高い状態で作業できる。昼休みはご飯も食べるので30分ぐらい開発できれば良いほうだが1,2コミットぐらいは確実にできたので十分良かった
  • 休日は自宅で開発するよりはカフェへ移動してしまった方が開発を始めやすい

モチベーション管理

  • 平日に仕事でもコードを書いて帰宅後もコードを書くのは気分的に辛い場合もあった(逆もあって、仕事で全然コードを書かない時期は個人開発が楽しかった)
  • Done is better than perfect. 大事
    • 個人開発は締め切りがない。いくらでも時間をかけられる
    • 自分は個人のモチベーションの絶対量には限界があると思っていて、細部に拘っている内に疲れて、最終的には開発自体に飽きてしまう(少なくとも自分はそう)
    • 特に自分の場合はCSS、Clean Architecture、Goらしい書き方、あたりに深入りしすぎる傾向があった
  • 日々の作業の区切りは、やり切る一歩手前ぐらいにする
    • 例えば、あとこの部分を修正したらコミットだなあと思ったら、そこを残しておいてその日は作業を切り上げる
    • 何をすればいいか明らかだと、別の日でも作業を始めやすい
    • たしか村上春樹職業としての小説家に書かれていたテクニックだった気がする
  • twitterで細かめに進捗を書くのもモチベーション高められたと思う(なお別に反応が貰えるわけではない)

開発の進め方

  • 機能定義→画面設計→テーブル設計→画面のコンポーネント設計→バックエンドのAPI定義→実際にコード書く
    • コードを書くまでの流れはよくある感じ
    • APIはswagger使ってきっちり定義しようともしたがswaggerの記法ルールとかバージョン互換がけっこう難しかったし、そもそもシンプルなAPIなので途中で諦めて開発しながら考えることにした(それであんまり問題なかった)
  • メモを取りまくる
    • 開発していて、「あれやらないとな」とか「これどういう意味なんだろう」とかすぐにやる必要がないタスクを思いついたら全てメモするように意識していた
    • 次にやらないといけないこと等もとにかく言語化してメモしていた
      • これは次に作業を始めるときにとても役立つし、全体の進捗を俯瞰して見れるので良い

実際には思うようにいかなかったこと

  • とにかくまずは空のページで良いので公開できることを目指した
    • が、現実にはDocker Composeをそのまま利用して外部にWebサービスを公開する手段はあまり確立されていない(というかメジャーじゃない)っぽくて調査や実験に相当な時間がかかって最終的には後回しにしてローカルだけで開発を続けた(辛すぎてモチベーションが枯れそうになったので)
    • そもそもDocker Composeは開発環境だけで使うものだよねって認識の人が多いということを初めて知ったりとか
  • 本当はDevOps周りきちんとしたかった
    • GithubにアップロードするとCycleCIでテストして自動的にデプロイしてくれるのを夢見ていた
    • CycleCIでテストとESLintで検査する部分までは、わりと開発初期に整備できていた
      • が、開発中盤まではGoのテストコードを書いていなかったしESLintの指摘も無視していたので常にBuildバッジは赤だった(残念)

ものを作ることの重要性

  • 技術書を読んだり、チュートリアルを写経したりするのも大事だが、何かを実際に作らないと分からないことが沢山ある
    • 例えばClean Architectureも実装してみて初めて気づくこと・理解できること・疑問に思うことも多い
    • あとWebサービスのようにフロントからインフラまで1人で開発していると、自分が疎い分野も明らかになるなと思った
      • 例えば自分はDBが弱くて、どういうSQLを書けばインデックスを効かせられるのか、インデックスが効いていることをどう調べて評価するのか、そもそもインデックスの仕組みとはみたいなところに興味を持つきっかけにもなった

今後

  • 今回は使用技術の学習も兼ねていたので開発期間が長くなってしまった。今後はWebサービスを素早く立ち上げるためのベストプラクティスを自分の中で整理して持っておきたいなと思った(多分Railsになりそう)

border-spacingで表の行列間の余白を調整する

この前初めて使った時に少しハマったのでまとめておく。

何をしたいのか

表の行列間に余白を作りたい。↓のように行と列で別々の余白を指定したい。

f:id:pokuwagata:20191022213739p:plain

MDN border-spacingより引用

marginpaddingはダメなのか

w3cによると

all elements except internal table elements

marginは効かない模様。

paddingはtdに指定できるので、これが解決策となる場合もある。

border-spacingを使う

表の行列間の余白を指定するために存在するプロパティ。行、列を個別に指定することも可能。

table {
  border-spacing: 1em .5em;
  border-collapse:separate;
}

border-collapse: collapseだとborder-spacingは効かないので注意。

border-spacingで表の位置がずれる問題

border-spacingは行間、列間だけでなく周りにも適用されるので、表の表示位置がずれる問題があるが、nagative marginで解決できる。

table {
  margin-top: -0.5em;
  margin-left: -1em;
}

参考

Can't perform a React state update on an unmounted component... エラーにConetextが原因でハマった話

環境

  • react 16.8.6

事象

React.useEffectを使用しているコンポーネントContext.Provider配下に配置したところ以下のエラーが発生した。

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function

コード例は省略するが、上記のエラー発生時にReact.useEffect内でAPIコール、その結果をsetState等すると何故かuseEffectの処理が無限ループする。

原因

本来はfunctional componentの外側でContextを定義するべきだが、以下のようにfunctional componentの内側で定義していた。

export const HogeContextProvider = (props: HogeProps) => {
  const HogeContext = React.createContext(null);
  return (...

以下が正しい。

export const HogeContext = React.createContext(null);

export const HogeContextProvider = (props: HogeProps) => {
  return (
      <HogeContext.Provider value={hoge}>
        {props.children}
      </HogeContext.Provider>
  );

functional componentの内側は、stateの更新等で何度もコールされるのでその度にContextを再作成していたことが原因の模様。

最初はContextをProvider配下でReact.useContextしていないことが原因かなと思って調べたら、そもそもContextをexportしていないことに気づいて、定義位置の間違いに思い当たった。

反省

DOMがstateに対応して更新されることは当然だが、functional componentの内部の処理が何度もコールされるイメージがなかった。どういうときにコールされるのか整理したい。

React、こういう単純なミスの原因特定が難しくてハマると解決に1,2時間ぐらいは必要になってしまうこともあり辛さがある。