無限にTODOアプリを作っていき
今回はReact+TypeScript+Spring Bootで書いてみる。
フロントエンド構成
- TypeScript
- React
- Webpack
- Jest (一応入れたけど、習熟度が足りず画面のIFがガンガンぶっ壊れるから現状テスト書いてない)
- bulma (CSSフレームワーク, そういえばhack使ってみたかったけど忘れてた)
- ts-lint (お前もうちょい機能強くなって。エディタ拡張とかエディタ拡張とか)
フロントその他ライブラリ
サーバサイド構成
- Spring Boot
- thymeleaf3
- とりあえず感のあるh2
- こちらもとりあえずDBに初期データ突っ込むだけのflyway
- というか素振りみたいな感じでやってて何も固めずにガンガン作ってるのでマイグレーションもクソもない。
サーバーサイドその他
- gradle (wrapper)
- spotless (フォーマッタ)
- spring-boot-devtools (すごい。楽。)
ヤケクソで進めている感じが否めないのですが、色々悩んでSpringのコード書き散らして、散々紆余曲折した後に
色々コードぶち壊してなんとなくDBに保存するまでできたわけなんですが。
ごらんのありさまだよ!!
もともと何をしようとしてたかというと
IndexedDB(もしくはlocalstorage)をベースに
オフラインで動くようなタスク管理アプリケーションを弄っていた。
(というかこの構成で毎回TODOアプリ作ってる気がする。進歩がない。)
で、なぜそうしたいかというと結局オフラインでも動くと面白い、それだけなんですが
このデータの同期をどうするかで悩んでしまった。
サーバーの状態とクライアントの状態が常に存在している。
なので少し、頭の整理のためにメモを書こうと思う。
ここから下は本当にメモ
例えば、サーバーにつながらない状態でタスクを追加したとする。
これはサーバーとつながったら同期されてほしい。
つまり、クライアントで表示はされているけど
クライアントしかデータを保持していない状態がある。
保存されていないデータがあれば何回もサーバにリトライかけて保存しにいくのかというと
現実そういうわけにもいかない。とりあえず5回ぐらいで考えてみるとする。
そういった場合には保存されていない状態で画面ではクルクル回ってるアイコンを出したい。
リトライ5回して失敗したら、エラー表示みたいなのを出したい。
エラー表示のアイコンを押したらエラーメッセージを出しておきたい。
サーバーとつながらない状態でタスクを削除したとする。
まぁ当然サーバーとつながらないので論理的に削除できない。
しかし、画面上消したい。
グレーアウトしておくとする。
これもクルクルとエラーアイコン欲しい。
更新は削除して書けって感じでとりあえず無視しておく。
画面読み込み時ないしリフレッシュボタン押下にデータを更新したい。
ここが頭の中で整理ができていない。
まずここで存在する可能性があるのが次の状態のタスク。
- 画面で追加したけどサーバーに永続化されていないタスク
- 画面で削除したけどサーバーから削除されていないタスク
- 別のPCから追加したけどローカルストレージには反映されていないタスク
- 別のPCから削除したけどローカルストレージには反映されていないタスク
1はサーバーに追加したい。
2はサーバーから削除したい。
3はローカルストレージに反映して表示したい。
4はローカルストレージに反映して削除したい。
どうするのが正解なのかいまいち分からないので
とりあえず案を上げてみる。
上記4つの状態を観点として考える
タスクの状態に画面で生成された <–> 永続化されたみたいな状態を持つ
これはローカルストレージのみに保存する。DBには持たない。
すると、ローカルストレージから読みだした際に、永続化されていないことは分かる。
あとは永続化されていないものを登録しにいくだけである。
でも削除された時はどうなのか?これも状態を持つとうまくいきそう。
下みたいなイメージならどうか。
画面で保持 –> 永続化 –> 画面で削除 –> 物理削除タスクを4種類にわけて画面で保持しておく。
上で書いた状態のストレージを画面上で4種類用意しておいて、画面上は同じ場所に表示
画面で保持 –> 永続化 –> 画面で削除 –> 物理削除
楽にうまくいきそう。画面側で頑張ればいい。
観点3に関しては読み込み時にサーバーから取ってきたものを永続化されてるとして
データを持っておけばいい。観点1, 2は何も考えなくてもよさそう。
観点4の状態が難しい気がしたけど、永続化されている情報を持ってきた際に
永続化されている情報と突合すればいい。
またもう少し図とか書いて練ってみる。
リポジトリはここ
oauthで遊ぼうかと思ったけど余裕がなかった。
というか使う必要性が現状なかったのでディレクトリ名変えなきゃ・・・
TypeScriptでモデルの型定義を良い感じに管理したい。
効率良くTypeScriptでドメインモデルの型定義を管理したい。
例えばここに、以下のようなツイッターのような投稿を模したモデルがあるとする。
type Post = { id: number, content: string };
ふむ。特筆すべきものはない。 ではこれをモジュールとして型だけexportすることにする。 一文増えた。
type Post = { id: number, content: string }; export = Post
ではこの型定義を利用した利用シーンを考える。 addPostという投稿を追加する関数を例で上げてみる。
import * as Post from "./model/post" const addPost=(post:Post)=>{/* some implementation */} ...
ふむ。良さそうに見える。
ではaddPostを利用するシーンに移ってみる。 ここではテキストエリアに書かれた内容をonClick時に投稿するシーンを想定してみる。 一緒に連番でIdを生成するような関数を想定する。
import {addPost} from "./addPost" let idCounter=0 const generateId = () => ++idCounter const onClick=(content:stirng) => { const id=generateId() addPost({id, content}) }
はて、contentはstringになってしまい、無味乾燥な型になっている。 このstringはなんのstringだっけ?どんな意味を本来持っているんだっけ?ということになりうる。
ここでPostの型定義をしたモジュールに対して色を加えてみようと思う。 次のようなコードだ。
type Post = { id: number, content: string }; declare namespace Post{ export type Id = Post["id"] export type Content = Post["content"] } export = Post
ちょっと面倒な感じがする。 では、先ほどの無味乾燥なソースに手を加えてみようと思う。
import {addPost} from "./addPost" import * as Post from "./model/post" let idCounter=0 const generateId:(() => Post.Id) = () => ++idCounter const onClick=(content: Post.Content) => { const id=generateId() addPost({id, content}) }
IdやContentの型がコードに表れており、カラフルなコードになったように思う。
さて、ここでリファクタリングのことを考える。 VSCodeのF2で出来るただのRenameのことである。
ここでPostのidの名前変えたくなった。screenIdにしたい。 F2でコードを変えるとどうなるか。
次のようになった。(まとめて書く)
type Post = { screenId: number, content: string }; declare namespace Post{ export type Id = Post["id"] // ここにエラーが発生する。 export type Content = Post["content"] } export = Post // .... import {addPost} from "./addPost" import * as Post from "./model/post" let idCounter=0 const generateId:(() => Post.Id) = () => ++idCounter const onClick=(content: Post.Content) => { const id=generateId() // ここにエラーが発生する。 addPost({screenId, content}) }
コメントで示したところはエラーが発生しており 手で編集する必要がある箇所が2か所発生する。
ここでは修正方法についてはそこまで難しいわけではないので解説しないが Id用の型があるおかげで、ある程度、楽に型を柔軟にメンテしやすくなるように思う。 他にいい方法があれば誰か教えていただければと思う。
Happy TypeScripting!
curlでログインしてAPIを叩く
Spring Bootで作った認証で保護されているAPIを叩く。
ログインページに入って、セッションを確立する
-c オプションでクッキーを保存する。
$ > curl -c my.cookie http://localhost:8080/login
以下みたいなクッキー(のファイル)ができるのでcatで確認
$ > cat my.cookie # Netscape HTTP Cookie File # http://curl.haxx.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. localhost FALSE / FALSE 0 XSRF-TOKEN 5f0854a9-e3ba-480c-91ed-e19505aecb08 #HttpOnly_localhost FALSE / FALSE 0 JSESSIONID A4D102840F355F72123DEACCDCD64941
-bオプションでできたクッキーを使ってログイン また、クッキーに書かれたXSRF-TOKENをヘッダに付与する
$ > curl -XPOST -b my.cookie http://localhost:8080/login/post -c my.cookie -H "X-XSRF-TOKEN:e76acbd1-234e-456e-970d-751e5a21c3c9"
認証で保護されているAPIを叩いてみる。
$ > curl -b my.cookie http://localhost:8080/user // APIのレスポンス {....}