修行編 - 1日目
やったこと
1. Django + Reactプロジェクトの計画
いつもは7日間のWeekly Sprintを組んで作業するのですが、今回はスピードが欲しいため3日間でSprintを切って細かく振り返りを行えるようにしました。
タスクの管理はGitHub Projectを利用しています。
2. APIドキュメントの作成
お決まりのSwaggerを利用してREST APIの雛形を作成しました。
ForkしたSwagger Editor Liveの設定と、Dockerを利用したモックサーバーの生成フローの導入までコピペでDone。
いい加減Yaml書くのツライなーという気持ちが高まってきたのでgRPC向けにprotoを書くことも考えたのですが、初めてのDjangoプロジェクトなのでベーシックなHTTP + RESTFulでがんばります
3. Flutterアプリの写経だん
inKinoというOSSのFlutterアプリが非常に完成度も高く勉強になるので、数日前から写経を行っていました。
ようやくReduxとUIの設定が終わり一通り動くところまで完成。
Viewの実装パターン等もフワッと理解できたので、近い将来にガチめなFlutterアプリをスクラッチで書くぞという機運が高まってきました。
FlutterでinKinoアプリを写経
— Andy (@andoshin11) 2018年6月1日
Storeパターンをざっくり理解できた気がする。やっぱUI綺麗だなー pic.twitter.com/WmpEodJzKf
これにてしばらくFlutterはお休みです。
4. ランニング
久々に走りました。東京に来てからは初めてです。
平日の昼間ということもあり人も少なく、外気も適温で絶好のコンディションでした。
が、、、
予想以上に体力の減退がひどく、3kmほど走ったところで死亡😇😇😇
続くかなこれ・・・
5. 帰省しました
京都に戻ってきました。
上賀茂神社で蛍を見る恒例のイベントも無事完了。月曜日まで初夏の京都を楽しむ予定です。
6. NBA Final観た
Game 1勝ちましたね。
サンキューJ.R.
明日やること
弟の誕生日だったり、入洛する祖母の案内だったりで作業時間取れないフラグがビンビン立ってます笑
- Next.jsでプロジェクト立ち上げ
- TypeScriptの設定
- React in patternsを読む
頑張るやで。
それではみなさん良い週末を!
修行編 - 0日目
ひょんなことから1ヶ月ほどゆっくりする時間ができました。
久々に納期の概念から解放されるまたと無い機会なので、今一度立ち止まってカラダとアタマを鍛え直そうと思います。
カラダを鍛える
走ります。
長く続けたバスケットを止めてからというもの、サイクリングの他には碌に運動というものをしてきませんでした。
体脂肪率8%の中学時代の身体をもう一度!とまでは言いませんが、まずはお腹周りの肉を落とす&持久力をつける意味で毎日ランニングをやってみます。
幸い家から3分で駒沢オリンピック公園があるので地の利を活かしたいです。ホコリを被ったFitBitも充電しなければ・・・
アタマを鍛える
新しい武器を身につけます。
書き慣れたRuby on RailsとVue.jsをしばらく封印して、他のスキルを業務レベルで使えるようにしたいです。
まずは基礎であるPython(Django)とReactをガッツリ復習して自信を持って仕事が受けられるようにするのが目標。作りたいサービスがいくつかあるので2週間くらいでカタチにするところまでかな。
現在取り組んでいるGo + Flutterのネイティブアプリも完成させたいですが、そちらはおそらく保留。後半の2週間はせっかくなのでScalaやRustのような新しい概念を学ぼうと思います。
そのあとはKotolinもやりたいしk8sも勉強したいしML Kitも触りたいしword2vecでも遊びたいし(ry
とにかく時間が欲しい。
生活を切り詰めて+もう1ヶ月お休みするのもアリなんじゃないかという気がしてきました(フラグ
ただし欲望の赴くままにあれもこれもと手をつけると中途半端な結果になりそうなので、戦略的にスキルアップを図っていきたいと思います。
というわけで明日は・・・
くらいまで進めれば最高ですね。
新幹線で京都に移動したり、両親と食事したりというイベントがあるので先行きは不安です笑
最後に
今回「ヒマ人になるよー」という話をしたところ、本当にビックリするほど多くの方から仕事のお誘いをいただきました。
プログラマとしての経歴も浅く東京に来てからの日も浅い自分にこれだけお声がけをいただけて、本当に感謝の気持ちでいっぱいです。
生憎そのすべてにはお応えできそうにありませんが、この機会にまたパワーアップしてより大きな価値を提供できるよう頑張ります。
改めて今後とも不肖Andyをどうぞよろしくお願い致します。
P.S.
関東近郊の方へ:
食事のお誘いをいただければいつでもどこでも飛んでいきます。
GoでWebアプリを作ろう 第一回 : Goで簡単なCRUD
こんにちは、Andyです。
普段はフロントエンドチームでJSばかり書いているのですが、せっかくGoの会社に入ったので良い機会だと思いGoに入門してみました。「Goの作法」を知ればより裏側のシステムについての理解が深まり、フロント側も良いプロダクトが作れるんじゃないかなと期待しています。
せっかく新しい言語を学ぶので、学習の中でやった事や詰まった事を文字で残そうというのが本記事の目的。
とてもじゃないですが1回で全てをカバーできないので数回に分けてチャレンジします。
手探りで自分なりのベストプラクティスを模索している最中なのでマサカリ大歓迎です。
現在のスタック
学習を始めるにあたって、自分のエンジニアとしてのスタックはこんな感じ。
- Ruby on Rails, ES6 (業務レベル)
- PHP, Perl, Python (趣味レベル)
ちなみにgolangの経験値はA Tour of Goを流し読みした程度です
作るもの
簡単なTODOアプリです。次回以降でユーザー認証機能も乗っけていきます。
2018年なので当然*1フロントはSPAで受ける事を考えて、JSONのI/Oを受けるAPIサーバーを想定しています。
またルーティングにGinを利用しています
プロジェクト構成
Railserとして一番始めにつまづくのがGoのプロジェクト構成です。いろいろと宗派があるようなので詳しくは触れません。
$GOPATH
は $HOME/dev/go
に設定しています。プロジェクトは $Home/dev/go/github.com/andoshin11/go-web-app
に配置しました。
アーキテクチャについてはDDDやその他のClean Architectureをベースとしたものも検討したですが、初めてのアプリなのでわかりやすくMVC(or MC?)デザインパターンを採用。
最終的なファイル構成はGitHubを参考のこと
パッケージ管理
依存関係の管理にはdepを利用
$ brew install dep $ dep init
Hello, world!
まずは基本となるControllerを src/controller
以下に作成します
// src/controller/index.go package controller import ( "net/http" "github.com/gin-gonic/gin" ) // IndexGET displays application index page func IndexGET(c *gin.Context) { c.String(http.StatusOK, "Hello, world!") }
上記をHandlerとして登録
// main.go package main import ( "github.com/andoshin11/go-web-app/src/controller" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", controller.IndexGET) router.Run(":8080") }
$ go run main.go
でサーバーを起動
ページが表示されました👏👏
Databaseの用意
DBはMySQLを利用していきます。
$ mysql -u root mysql> CREATE DATABASE gwa;
なにかと衝突しそうな名前だけどとりあえずDBを作成。
$ go get github.com/pressly/goose $ mkdir db $ touch db/dbconf.yml
// db/dbconf.yml development: driver: mymysql open: tcp:localhost:3306*gwa/root/hogehoge
DBの情報が正しいか疎通確認
$ goose status goose: status for environment 'development' Applied At Migration =======================================
テーブルの作成
gooseで task
テーブルを作成します
$ goose create createTask sql
上記のコマンドを実行するとタイムスタンプを名前に含んだマイグレーションファイルが db/migrations
以下に作成されるので、そちらにSQLを追記
-- db/migrations/2018......createTask.sql -- +goose Up -- SQL in section 'Up' is executed when this migration is applied CREATE TABLE IF NOT EXISTS task ( id INT UNSIGNED NOT NULL, created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), title VARCHAR(255) NOT NULL, PRIMARY KEY(id) ); -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back DROP TABLE task;
$ goose up
を実行してマイグレーションを実行し、テーブルが作成されていることを確認します
ORM(?)の選定
MySQLとの接続をラップしてくれるORMとしてGORMやsqlxなどを検討したものの、
「いちいちstructのメンテやってられんわ」
という強い理由によりxoを採用しました。
現実問題としてファットモデルも避けたかったし、基本的なCRUDを記述することだけ考えると必要十分な気がします。
$ go get github.com/knq/xo $ mkdir src/model $ mkdir -p db/xo/templates $ cp $GOPATH/src/github.com/knq/xo/templates/* db/xo/templates/ $ xo mysql://<user>:<pass>@<host>/<db> -o src/model/ --template-path db/xo/templates/
上記の手順を実行すると src/model/**.xo.go
というファイルが作成されます。 goosedbversion.xo.go
は必要ないので削除して大丈夫です。
Modelの接続
xoコマンドで各テーブルのmodelファイルが作成されたのでアプリケーションに接続していきます。まずは src/model/model.go
を作成してDBインスタンスを返す関数を記述。
// src/model/model.go package model import ( "database/sql" "log" "os" // mysql driver _ "github.com/go-sql-driver/mysql" ) // DBConnect returns *sql.DB func DBConnect() (db *sql.DB) { dbDriver := "mysql" dbUser := "root" dbPass := os.Getenv("MYSQL_ROOT_PASSWORD") // 環境変数から取得 dbName := "gwa" dbOption := "?parseTime=true" db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName+dbOption) if err != nil { log.Fatal(err) } return db }
続いてTODOの一覧を返すコントローラーアクションを記述。
xo
の設定をしておいてなんですが、今回だけControllerにデータ取得処理を直接書きました。本来なら template
を拡張すべきですが、実際のユースケースとして任意のテーブルのレコードを全取得するケースは中々ないので無視。
// src/controller/task.go package controller import ( "database/sql" "fmt" "net/http" "time" "github.com/andoshin11/go-web-app/src/model" "github.com/gin-gonic/gin" ) // TasksGET returns list of tasks func TasksGET(c *gin.Context) { db := model.DBConnect() result, err := db.Query("SELECT * FROM task ORDER BY id DESC") if err != nil { panic(err.Error()) } tasks := []model.Task{} // iterate result for result.Next() { task := model.Task{} var id uint var createdAt, updatedAt time.Time var title string err = result.Scan(&id, &createdAt, &updatedAt, &title) if err != nil { panic(err.Error()) } task.ID = id task.CreatedAt = createdAt task.UpdatedAt = updatedAt task.Title = title tasks = append(tasks, task) } fmt.Println(tasks) c.JSON(http.StatusOK, gin.H{"tasks": tasks}) }
返り値はリスト形式のJSONです。 main.go
で新たなnamespaceを作成して上記のHandlerを登録
// main.go ... func main() { router := gin.Default() // API namespace v1 := router.Group("/api/v1") { v1.GET("/tasks", controller.TasksGET) } router.GET("/", controller.IndexGET) router.Run(":8080") }
サーバーを再起動してエンドポイントが有効なことを確認。当然結果は空っぽです。
雑にレコードを突っ込んでレスポンスを再確認
良さそう
タスクを追加する
そういえば自動採番の設定を忘れていたので新しいmigrationを追加
$ goose create autoIncrementTask sql
// 2018....autoIncrementTask.sql -- +goose Up -- SQL in section 'Up' is executed when this migration is applied ALTER TABLE task MODIFY id INT UNSIGNED AUTO_INCREMENT NOT NULL; -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back ALTER TABLE task MODIFY id INT UNSIGNED NOT NULL;
$ goose up
xoのアップデートも忘れずに行います。
task
コントローラーにHandlerを追加
// src/controller/task.go ... // TaskPOST creates a new task func TaskPOST(c *gin.Context) { db := model.DBConnect() title := c.PostForm("title") now := time.Now() task := &model.Task{ Title: title, CreatedAt: now, UpdatedAt: now, } err := task.Save(db) if err != nil { panic(err.Error()) } fmt.Printf("post sent. title: %s", title) }
// main.go ... v1.POST("/tasks", controller.TaskPOST) ...
DBにレコードが追加されるようになりました
タスクの更新
新しいHandlerを task
コントローラーに追加
// src/controller/task.go ... // TaskPATCH updates a task func TaskPATCH(c *gin.Context) { db := model.DBConnect() id, _ := strconv.Atoi(c.Param("id")) task, err := model.TaskByID(db, uint(id)) if err != nil { panic(err.Error()) } title := c.PostForm("title") now := time.Now() task.Title = title task.UpdatedAt = now err = task.Update(db) if err != nil { panic(err.Error()) } fmt.Println(task) c.JSON(http.StatusOK, gin.H{"task": task}) }
// main.go ... v1.PATCH("/tasks/:id", controller.TaskPATCH) ...
更新成功!
タスクの削除
同じ要領でコントローラーに追記
// src/controller/task.go // TaskDELETE deletes a task func TaskDELETE(c *gin.Context) { db := model.DBConnect() id, _ := strconv.Atoi(c.Param("id")) // Check if record exists task, err := model.TaskByID(db, uint(id)) if err != nil { panic(err.Error()) } err = task.Delete(db) if err != nil { panic(err.Error()) } c.JSON(http.StatusOK, "deleted") }
// main.go ... v1.DELETE("/tasks/:id", controller.TaskDELETE) ...
削除も問題なくできました。
まとめ
複雑な機能は何もありませんがとりあえずのAPIサーバーができました。Go楽しいですね。
第二回以降はログイン機能とかモデルの関連付けとか作業の自動化とかやりたいです。
それではまた次回
*1:text/templateとか使ってる人類いるんですか?
君は2クリックでVue appを立ち上げたことがあるか!!<CodeSandboxを世の中に広めなければと思った件>
ちょっと時間が経ってしまったのですが、2月にアムステルダムで行われたVue conf AmsterdamとFrontend Developer Love conferenceに参加してきました。
初めての技術カンファレンス&海外でのイベントということで学ぶことばかりの数日間だったのですが、今回はその中でも特に印象に残ったセッションをご紹介します。この記事を読んで「CodeSandboxおもしろそう!」「使ってみたい!!」という方が1人でも増えると嬉しいです。
CodeSandboxとは
CodeSandboxとはオンラインで動くコードエディターで、ReactやVue、Angularなどのフレームワークですぐにアプリケーションを作成できるのが特徴です。
GitHubでオープンに開発が行われているのですが、なにより驚いたのがプロジェクトリーダーのIvesがまだ21歳の学生であること!まだ日本ではあまり知られていないプロダクトですが、将来がとても楽しみなのでこのCodeSandboxの便利な機能をいくつかご紹介したいと思います。
2クリックでアプリを立ち上げる
まずはCodeSandboxのページを開き、右上の"Create Sandbox"をクリックします
するとフレームワークを選択する画面が出てくるので、Vueのアイコンをクリックします
完成です👏👏
Lower the learning curve.
Installing tooling should not stand in the way of getting started
という言葉にもあるように、CodeSandboxでは「コードを書きたい!」と思った人がなるべくスムーズに開発できよう徹底的にしきいを下げる工夫が行われています。
画面に並んでいるのは左端から順にファイルツリー、エディタ、ミニブラウザです。
もちろんここにNPMパッケージを追加したり import/export
を記述することも可能です。
試しに簡単なButtonコンポーネントを作成してみましょう
propsで受け取った text
を表示するだけのbuttonです
ローカルで開発するのと全く同じ感覚で表示までできました
画面下部のコンソールにエラーも出力してくれます
外部ライブラリを読み込もうとすると怒られますが、
ボタンをクリックするだけで自動でライブラリがダウンロードされ、 package.json
にも追記してくれます。
またCodeSandboxの特徴としてライブラリの設定ファイルをGUIからconfigできる機能があります。各種設定の概要を確認しながら調整できるのでなかなか便利です
さらにさらに、ライブラリの追加もGUIの検索ボックスから行えます(バージョンも指定できる)。このお手軽感めっちゃ好き❤️
Encourage sharing & discovery
CodeSandboxで作成したアプリは簡単に共有ができるだけでなく、お目当のプロジェクトを見つけるのも簡単に行えます。
せっかくなので <iframe/>
でエディタを埋め込んでみました。みなさんも実際に僕が書いたコードを確認できるかと思います。ちゃんと動作もするはず・・・
共有方法は簡単。
左上の"share"をクリックすると埋め込みリンクやエディタの共有リンクがすぐに取得できます。
また、CodeSandbox上から直接GitHubのレポジトリを作成してpushすることも可能です
リポジトリができた!!
他のプロジェクトを探したい時はSearchページから検索するだけ。プロジェクト名だけでなく使っているフレームワークやライブラリからも絞りこむことができます
例えばElement UIを使っているプロジェクトならこんな具合に。
プロジェクトのページに飛べば実際にどのように動作して、どのようにコードが書かれているのか学べます
Easy to import
今までに作成したプロジェクトをCodeSandboxに取り込むのも簡単です。方法としては大きく
という3つの方法が用意されていますが、今回はDEMOとしてGitHub importを試してみましょう。
以前作成したVue ToDo List sample(https://github.com/andoshin11/vue-todoList-sample)というレポジトリがあるのでこちらでお試し。
importするにはこのURLを貼り付けるだけ。あとは自動でCodeSandbox上にワークスペースが作成され、GitHubと同期された状態になります。
一瞬でimportが完了しました。こちらも <iframe/>
で埋め込んでみるので是非触ってみてください
https://codesandbox.io/s/github/andoshin11/vue-todoList-sample/tree/master/
Local Editor Experience
CodeSandbox上ではmonaco-editorが動いています。
monaco-editorはブラウザ上で動くVS Codeのようなものです。そこで vetur
をもとにした monaco-vue
が動作しているため、まるでVS Code上で開発しているかのような体験がブラウザで行えるのは良いですね。
その他の機能
ここでは紹介しきれないほどまだまだ機能がたくさんあるのですが、主だったものだけ箇条書き
- Parcelサポート
- preactサポート
- svelteサポート
- Sassサポート
- TypeScriptサポート
- Prettierデフォルトサポート
- ES Lintデフォルトサポート
- Liveコラボレーション機能
- Jest実行サポート
- 静的ファイルホスティング
- HMR
- Emmetサポート
- zipエクスポート
などなど素敵なものがたくさん。OSSなので日々機能が追加されていくのも魅力の一つです。
まとめ
CodeSandboxめっちゃ良い👏👏
簡単なハンズオンやデモをする際にサクッと環境が立ち上がるのが良いですね。そしてシェアの手軽さも嬉しい。
VueやReactの楽しさを知る前にnpm, webpack, babel等の設定でドロップしてしまった人も多いと思います。これからはCodeSandboxをスッと差し出して「とりあえず触ってみて!」と言えるようになると素敵ですね。
普通に開発環境として優秀なので出先でGitHubからプライベートモードでimport → 開発 & デバッグ → 動作確認までワンストップで行えそうで期待大です。
まだリリースされて1年ほどなので今後がとても楽しみ。みなさんもこの機会にぜひ試してみてください!!
おまけ
CodeSandboxへのお布施やIvesへの投げ銭はこちらからどうぞ👇👇
Pull RequestごとにStorybookがビルドされたら最高じゃね?
merpayでは積極的にStorybookを活用してコードと見た目の両方の観点でレビューを行なっていますが、Pull Requestが飛んでくるたびに該当ブランチをpullしてローカルでStorybookを起動するのが大変という運用上の問題を抱えていました。
そこでソリューションチームの協力のもと、PRが作成されるたびにCIからGAE上にユニークなpathをもったStorybookを配信する仕組みが導入されたのが先週のこと。とてもめでたい👏👏
これによってPRのデザインをチェックする時もURLをクリックするだけでよくなりました。
UI開発の概念がひっくり返るほど便利だったので、この仕組みをプライベートの開発でも導入したいと思います。
やりたいこと
- Storybookをホスティングする環境(URLの共有だけで完結するもの)を作りたい
- Pull Requestが作成されるごとに自動で新しいURLを発行したい
- Pull Requestがクローズされたら上記の環境を自動で破壊したい
これらの要件を達成するため始めは社内のスクリプトを丸ごとパクろうかと思いましたが、プロジェクトごとにGCPを設定するのはなかなか大変なので別の方法を模索してみます。
Heroku Review Apps
Heroku Review Appsとは、Herokuにpipelineを設定することでPull Requestが作成されるたびに独自のステージング環境を作成してくれるものです。
さらにPull Requestがクローズされたらそのステージング環境を一定時間後に破棄してくれるなど、今回の要件にピッタリの機能が詰まっています。こいつを今回は使っていきましょう!
プロジェクトの立ち上げとStorybookの設定
いつものようにvue-cli 3.0でプロジェクトを立ち上げました。
RouterやStoreの設定を自動でやってくれるのでいつも重宝しています。以下の記事を参考にしてこちらにStorybookを設定。
シュッとButton componentを作成します。
GitHubレポジトリとHerokuアプリの作成
GitHubにレポジトリを作成して master
をpush。Heroku側でもアプリを作成して接続します
StorybookをビルドしてExpressで配信する
package.json
にコマンドを一行追加。
"build-storybook": "build-storybook -c .storybook -o storybook-static"
このコマンドを実行することでStorybookが静的なページとしてビルドされますが、このままではHerokuで公開できません。
そこで今回はExpressをHeroku上で動かしてそこから配信してみようと思います。まずは Express
を追加。
$ yarn add express
server.js
を作成
// server.js var express = require("express"); var path = require("path"); var serveStatic = require("serve-static"); app = express(); app.use(serveStatic(__dirname + "/storybook-static")); var port = process.env.PORT || 5000; app.listen(port);
そしてHerokuにデプロイする時に実行されるコマンドを package.json
に追記します
"heroku-postbuild": "npm run build-storybook", "start": "node server.js"
heroku-postbuild
はHeroku上で自動で npm install
が走ったあとに実行されるフックで、 start
はデプロイ完了時にトリガーされるものです。
最後にHeroku上でAutomatic DeployをEnableにし、 master
ブランチがpushされたら自動でデプロイが走るように設定します。
この状態で master
をpushもしくは手動デプロイし、ビルドされたStorybookが無事にExpressから配信されていることを確認
これで最新のStorybookがHerokuから配信されるようになりました🎉
Heroku Review Appsの設定
ここからはPRごとに配信を行うためにHeroku Review Appsの設定を行なっていきます。まずは "create new pipeline"で storybook-review-app
という名前のpipelineを作成。(ここの名前は自由です)
そうするとpipelineの管理画面が出てくるので画面左端より "Enable Review Apps..."を選択
Enableにするとプロジェクト内で app.json
ファイルを作成するように言われます。 app.json
の中身はGUIからポチポチ設定できるのですが、今回は特に触らなくて大丈夫です。Buildpackが nodejs
になっていることだけ確認しておいてください。
最下部の "Commit to Repo"を押すとプロジェクトの master
ブランチに自動で app.json
がcommitされます(ちょっと気持ち悪い)
最後にReview Appの設定を行なって完了です。Destroyの期限は最長30日まで設定できますがとりあえず1日で問題ないでしょう。
PRを作成してみる
Heroku Review Appsがちゃんと動作するか実際にテストしてみましょう。ボタンの文言だけ変更する簡単なPRを作成
するとpipeline上で自動でアプリのビルドが始まります
ログも見れちゃう。
ビルド完了。
すごい!新しいバージョンが配信されてる!!
そしてPRのスレッド上にもデプロイが完了した旨の通知が届いています。これをSlackで受け取ったりすると良さそうですね。
URLもおしりにPRナンバーが付与されており、ユニークであることがわかります。
PRをクローズする
PRをマージしてクローズしてみました
一瞬で環境が消えた😲めっちゃ優秀
まとめ
Heroku Review Appsが予想の数段上を行く優秀さでした。これでいつでも開発メンバーやデザインチームの手を煩わせることなくレビューしてもらうことができます。Heroku自体にBasic認証などを設定しておけばプライベートプロジェクトでも大丈夫。
みなさんも(Herokuの無料枠に注意して)どんどんStorybookを育てていきましょう。今回のコードは下に貼っておきます。
P.S.
HerokuでNode.jsが動くならそもそも毎回静的ファイルをビルドしなくて良かった説ある...