TypeScript, Go, Docker Composeに入門するために約半年間かけてWebサービスを構築した話
この記事はチームスピリット アドベントカレンダー2019の22日目の記事です。
今年(2019年)の7月から12月にかけて約半年間、プライベートでコツコツ開発していたWebサービスが一応形になった。技術的な内容は別の記事で書く予定なので、今回は半年間を通じて感じられた個人開発という営み自体の難しさや継続のコツについて書いてみる。
作ったもの
これ何
- ブログのような何か
- 認証認可、記事一覧、投稿、削除ができる
構成
- ECSは使わず、EC2インスタンス1台の上でDocker Composeを動かしている
モチベーション
- 0からWebサービスを構築してみたい
- (業務ではSalesforceを使っているので)たまにはSalesforceから離れたい
- TypeScriptやGoで、それなりの量のコードを書きたい
なお、ブログサービスは題材として良さそうだっただけで、サービスとして何かやりたいことがあったわけではない...
所感
実際にやってみて感じたこと
時間確保の難しさ
- 平日の日中は仕事があるので、平日の朝・夜か休日に開発
- とにかく15分でもいいから毎日確実に作業することが凄く大事
- 休日だけ開発をすると、始める際に「先週何をしていたか思い出す」負担が大きくて効率が悪い
- 意外と良かったのが昼休みに開発することで、仕事モードのまま開発を始めると凄く集中力が高い状態で作業できる。昼休みはご飯も食べるので30分ぐらい開発できれば良いほうだが1,2コミットぐらいは確実にできたので十分良かった
- とにかく15分でもいいから毎日確実に作業することが凄く大事
- 休日は自宅で開発するよりはカフェへ移動してしまった方が開発を始めやすい
モチベーション管理
- 平日に仕事でもコードを書いて帰宅後もコードを書くのは気分的に辛い場合もあった(逆もあって、仕事で全然コードを書かない時期は個人開発が楽しかった)
- Done is better than perfect. 大事
- 個人開発は締め切りがない。いくらでも時間をかけられる
- 自分は個人のモチベーションの絶対量には限界があると思っていて、細部に拘っている内に疲れて、最終的には開発自体に飽きてしまう(少なくとも自分はそう)
- 特に自分の場合はCSS、Clean Architecture、Goらしい書き方、あたりに深入りしすぎる傾向があった
- 日々の作業の区切りは、やり切る一歩手前ぐらいにする
- twitterで細かめに進捗を書くのもモチベーション高められたと思う(なお別に反応が貰えるわけではない)
— 閉口 (@yn2011) 2019年10月3日
開発の進め方
- 機能定義→画面設計→テーブル設計→画面のコンポーネント設計→バックエンドのAPI定義→実際にコード書く
- メモを取りまくる
- 開発していて、「あれやらないとな」とか「これどういう意味なんだろう」とかすぐにやる必要がないタスクを思いついたら全てメモするように意識していた
- 次にやらないといけないこと等もとにかく言語化してメモしていた
- これは次に作業を始めるときにとても役立つし、全体の進捗を俯瞰して見れるので良い
実際には思うようにいかなかったこと
- とにかくまずは空のページで良いので公開できることを目指した
- が、現実にはDocker Composeをそのまま利用して外部にWebサービスを公開する手段はあまり確立されていない(というかメジャーじゃない)っぽくて調査や実験に相当な時間がかかって最終的には後回しにしてローカルだけで開発を続けた(辛すぎてモチベーションが枯れそうになったので)
- そもそもDocker Composeは開発環境だけで使うものだよねって認識の人が多いということを初めて知ったりとか
- 本当はDevOps周りきちんとしたかった
- GithubにアップロードするとCycleCIでテストして自動的にデプロイしてくれるのを夢見ていた
- CycleCIでテストとESLintで検査する部分までは、わりと開発初期に整備できていた
- が、開発中盤まではGoのテストコードを書いていなかったしESLintの指摘も無視していたので常にBuildバッジは赤だった(残念)
ものを作ることの重要性
- 技術書を読んだり、チュートリアルを写経したりするのも大事だが、何かを実際に作らないと分からないことが沢山ある
今後
border-spacingで表の行列間の余白を調整する
この前初めて使った時に少しハマったのでまとめておく。
何をしたいのか
表の行列間に余白を作りたい。↓のように行と列で別々の余白を指定したい。
margin
やpadding
はダメなのか
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時間ぐらいは必要になってしまうこともあり辛さがある。
Go modules利用時にvscode-goのinferGopathが無効化される理由
環境
事象
- vscode-goにはワークスペースやユーザ毎に
$GOPATH
を推測するgo.inferGopath
というオプションがあり、自分は常に有効化している(VSCodeでGoのデバッグにも以前に書いた) - しかし、Go modulesを使用しているディレクトリ(go.modが配置されている)をVSCodeで開くと下記のエラーが表示された
The "inferGopath" setting is disabled for this workspace because Go modules are being used.
原因
- Go modulesを使用している場合は
$GOPATH
の設定が不要だから - Go modulesを使用する利点の1つは
$GOPATH
を通さず、$GOPATH
配下外に作成されたディレクトリ上で外部の依存パッケージを扱えること- しかし、Goの
import
は$GOPATH/src
や$GOROOT
からパッケージを探す仕様だったはず...- ちなみにgo.modが直下になるように
$GOPATH
を設定すると$GOPATH/go.mod exists but should not
とエラーが表示される
- ちなみにgo.modが直下になるように
- 自分で定義したパッケージをimportするときはどうすればいいのか?
- しかし、Goの
Go modules使用時に自分で定義したパッケージをimportしたい
- 答え:User-written packages fail to import with go.mod. GO111MOD=on
go.modのモジュール名/パッケージ名
でimportできる- 結局、importからは
$GOPATH/src/モジュール名
に見えているっていう理解でいいのかな... - ちなみにモジュール名は実際のディレクトリ名と異なっていても良い模様
- 結局、importからは
その他
使用しているAlfred Workflows一覧
環境
- Alfred 4.0.3
Alfred Powepackを利用している場合は2台のMacでAlfredの設定を同期することも可能(ドキュメント)だが、1台からの片方向同期かつWorkflowsだけを同期するのは難しそうなので、業務で使用するMacとは手作業で同期を行うことにしようと思った。(JIRAチケットへのジャンプ等、業務でしか使わない設定も少なくないので...)
今後も良さそうなWorkflowsがあったら、このドキュメントに追加していく。
Workflows
(自作)フォルダを検索してターミナルで開く
- filefilter(public.folder) → Browse in Terminal
Docker Compose上で起動したwebpack-dev-serverに設定されたプロキシが動作しなくてハマった話
環境
- macOS 10.14.4
- docker-compose version 1.23.2
- webpack-dev-server 3.7.2
- webpack 4.37.0
フロントエンド(webpack-dev-server)、バックエンドをそれぞれDocker Compose上にコンテナとして起動して開発していた際にハマったお話。
事象
webpack-dev-serverはホストするjs内で発行されるHTTPリクエストをプロキシすることができる
webpack.config.js
を以下のように設定(抜粋)- APIサーバーは5000番ポートを開いているとする
devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 8080, historyApiFallback: true, proxy: { '/api': 'http://localhost:5000', }
- 上記設定を反映したwebpack-dev-serverを起動し(
docker-compose up
)、実際にlocalhost:8080にアクセスしてfetch('/api')...
をブラウザから実行すると、webpack-dev-serverが以下のエラーを出力した
[HPM] Error occurred while trying to proxy request /api from localhost:8080 to http://localhost:5000 (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)
- httpレスポンスは以下(抜粋)
504 Gateway Timeout Error occured while trying to proxy to: localhost:8080/api
原因
Docker Compose上のコンテナが参加するネットワークでは、
docker-compose.yml
上のサービス名で名前解決が行われるため。従って、
webpack.config.js
を下記のように書き換えると上手くいく
... proxy: { '/api': 'http://api:5000', }
- なお、
docker-compose.yml
では以下のようにサービス名をapi
としている
version: "3.4" services: api: build: context: ./api ...
敗因
- 書いてみるとこれだけのことだが、多分2,3時間ぐらいハマっていた
- サービス名で名前解決ができること自体はDocker Composeのドキュメントに書いてあることだし、以前から知っていた。なぜ気づけなかったのか...
- 多分だが、ブラウザからDocker Compose上のwebpack-dev-serverにアクセスする際は、
- のでwebpack-dev-serverが動作しているのがDocker Compose上であることを忘れていたというか、しっかり認識できていなかったような気がする。
- また、Docker Compose上のネットワーク内のコンテナ間もlocalhostで通信できるようなイメージだったのも原因かもしれない。
- あと、ステータスコード504 Gateway Timeoutの理解が不足している感もある。webpack-dev-serverがlocalhost:5000が存在しないためにレスポンスを得られない→ポート5000で動作しているはずのHTTPサーバーのログを確認する→ログがない→接続先がおかしい?というような手順をスムーズに踏めたら良かった。
- Goで書いているHTTPサーバは外部ファイルへのログ出力を未実装で、結局調査のために書いたんだけど、ここを面倒くさがらずに早く実行するべきだったかな...
bashコマンドは読取権限のあるファイルなら処理できる
環境
Permission denied
$ touch hoge.sh && ./hoge.sh
するとbash: ./hoge.sh: Permission denied
になる- 実行権限がない
- しかし
$ touch hoge.sh && bash hoge.sh
は正常終了するのはなぜか?