ナンプレSPAを vue + TypeScript で作った話 〜フロントエンドにクリーンアーキテクチャを適用する〜
- ナンプレSPA(number place infinity)とは
- ※注意
- 始め方
- ルール
- 遊び方
- 使った技術
- デプロイ環境
- 作った経緯
- アーキテクチャ
- 課題
- 学んだこと・経験したこと
- 11/30の発表で言わなかったこと
※本記事は2020/11/30のゆるWeb札幌にてナンプレについて発表させていただいた際の資料をブログ記事に落としたものです。
ナンプレSPA(number place infinity)とは
ブラウザで動作するナンプレのアプリです。 静的なSPAとなっており、ページ取得時以外でサーバーとの通信は行いません。
※注意
- 選んだ盤のサイズが大きい場合、高い負荷がかかり問題の生成に時間がかかる場合があります。
- 30秒ほど待っても問題が生成されない場合はブラウザのタブを閉じるか、戻るボタンやリロードボタンを押下し、初期画面に戻りやり直してください。
- 負荷が掛かるせいでブラウザの他のタブに影響がないと言い切れないため、作業中のタブがある場合は作業を完了するなどしてから問題の生成を行ってください。
始め方
- まるい数値のボタンで盤の大きさを決める
- STARTボタンを押す
9x9や10x10など大きめの問題は、生成に時間がかかります。 生成中はフリーズしているように見えます。20秒くらいは待ってください。 待ちきれない場合はブラウザの当該タブを閉じて最初からお試しください。
ルール
1~nの数値を縦・横・太枠の四角いエリアの中で重複しないように埋めていき、全てのマスを埋められたら成功です。
遊び方
カーソルの動かし方
- マスをタップまたはクリック
- タッチムーブ(画面上のどこでもタッチしたまま指を動かすことでカーソルを動かせます)
- キーボードの矢印キー
数値の入力
- 画面右下の半月状のコントローラの数値をタップ
- キーボードの数値キー
数値の削除
- 画面右下の半月状のコントローラの×をタップ
- キーボードのBackSpaceキー
使った技術
デプロイ環境
完全に静的で通信を行うことのないブラウザアプリになってます。
作った経緯
→ナンプレを解くライブラリを作ってみよう!
- 2019/10
- ゆるゆると作り始める
- 2019/12
- 問題を解くロジックが完成 →白紙から解いたら問題を生成できるのでは?
- 2020/1
- 問題を生成するロジックが完成 →UIも作ってみるか〜
- 2020/2
- UI作って公開
アーキテクチャ
- クリーンアーキテクチャを意識している
- CoreとApplicationは純粋なTypeScriptのクラスで構成されている(アノテーションのみTSyringeに依存している)
- データを管理する部分はDIで実装
- Viewは表示と入力受付に徹する
Core/Application/View 分離のメリット
詳しくは書籍「クリーンアーキテクチャ」参照のこと!
私の感じたメリット
- 重要かつ複雑なCoreとApplicaitonを扱いやすく作れた
- Viewの都合による変更が入り込まない
- UIを無視し単体で試験
- 永続化処理部分をMock化して開発
- 決定の遅延
課題
- ゲームはできるが楽しさややり込み要素が足りない
- もうちょい素敵なコントローラーにしたい
- CoreとApplicationとViewのプロジェクトを別々にしたい
- 問題生成処理性能改善
- レガシーコードからの脱却
- テストを書いて無理やり動かしたクソコードと複雑な設計
- 理解不足のDDDプラクティス
学んだこと・経験したこと
- 複雑なロジックにテストを書いて立ち向かう
- Core部分が複雑でテストがないと完成させられなかった
- フロントエンドもクリーンアーキテクチャは有用
- 決定の遅延 / 開発速度UP / テスト容易性UP
- フロントエンドに DDDのプラクティス は有用か?
11/30の発表で言わなかったこと
追加で言いたいことを言いまくります。
背景画像はアイヌ文様
背景画像はアイヌ文様のフリー素材のモレウというサイトから拝借しています。 モレウ様ありがとうございます!
ちょっとしたことで使いにくさが解消された
選択中のセルをハイライトする際、初めはセルの背景色を薄いピンク色で点滅させていました。 スタイルでこんなことできるのかーという気持ちで採用したピンク点滅ですですが、これを辞めたことでグッと使いやすくなったと思いました。 ピンク点滅だった頃は、遊んでいると疲れてしまい、楽しむ気になれないほどでした。 これをやめてから、自分自身もしっかり楽しめるようになりました。
おすすめナンプレアプリ
ナンプレアプリを作っておいてなんですが、以下のiPhoneアプリが好きです。おすすめです。
apps.apple.comナンプレアプリを自分で作るまでは中級までしか解けませんでしたが、アプリを作ってからは超上級の「極」まで解けるようになりました。もちろん自分で。
クリーンアーキテクチャやDDDの「プラクティス」って何?
あえて「プラクティス」と言いました。 クリーンアーキテクチャやDDD「風」に作っており、厳密には違うかもです。 ですが、巷で「軽量DDD」と呼ばれている設計のテクニックが存在しており、ここではそれらを「プラクティス」と表現しました。
クリーンアーキテクチャやDDDやそれらのプラクティスを正しく理解しているかと言われると、僕は全く自信はないです。僕はDDDを経験したことがありません。ま、一人で開発してるってことは、自分がドメインエキスパートなんですが。 いつかは本当にDDDをしてみたいですね。
自分が「良い」と思えば良いんです。このアプリはある程度「クリーン」です。 とは言え、現状に満足しているわけではありません。自己研鑽あるのみです。
DIコンテナのTSyringeについて
実は使用したのはこのアプリでのみです。 実務で使ったことはなく、今後使うこともないように思います。
というのも、このTSyringeはアノテーションでDIする際に、コンストラクタがpublicでなくてはならず、僕の設計方針に合わないからです。 あと、単純に、あまり使い勝手が良くないような。
そして、過去に携わった業務においてTSyringeの使用を拒否された(当時現場でフロントエンド全般の学習コストが問題視されていた)際、自分でコンテナを作りDI(アノテーションは使いませんでしたが)するようにしたところ、とてもシンプルで良いものができたので、それからコンテナを自作するようになりました。 自作コンテナと言ってもただのグローバル変数のようなものなんですけどね。
僕の設計方針:static creation
クラスのほとんど全てを、private constructorとし、そのクラスに定義するpublic static create() などの生成メソッドを使用してインスタンスを取得するようにしています。 同じクラスでも生成するシチュエーションによって生成方法や生成したインスタンスの使用目的が異なる場合が多々あります。 これに対応する名前を付けた静的ファクトリーメソッドを使用することで、可読性が高く、少しだけ柔軟な設計が可能となります。
このちょっとした柔軟性の高まりで後々救われることがあります。 やらない理由はないです。必ずそうします。
このテクニックは「static creation」と呼ばれているそうです。 2019年秋に仙台で行われたTDDBCに参加しt_wadaさんにレビューいただいた際にそう呼ばれておりました。間違いないです。
このテクニックは、例えばこんな感じで使っています。
class User< TFor extends 'for-create' | 'for-reconstruct', TId extends string | undefined = TFor extends 'for-create' ? undefined : string > { public static create(name: string): User<'for-create'> { return new User(undefined, name); } public static reconstruct(id: string, name: string): User<'for-reconstruct'> { return new User(id, name); } private constructor(public readonly id: TId, public readonly name: string) {} } const userForCreate = User.create('John'); const userForUpdate = User.reconstruct('123', 'Mike');
新規登録時に作るUserはidを持っていませんが、DBやAPIで取得したUserはidを必ず持っています。 同じクラスのインスタンスですが、異なる性質を表現できます。 いちいちID持ってるかどうかの判定をしたくありませんよね。 無い場合は絶対に無い、有る場合は絶対に有るんですから。 (idをクラス化するケースもありますけどまぁそれはおいておいて)