まえがき
ちょうど3年前に「初心者3人でwebサービス(webアプリ)を作ったので、立ち上げからリリースまでを時系列に書いていく」なる記事を書いたことがあり、いまだにいいねをつけてもらえることがあります。そこで、3年の時を経て似たような記事を書いてみようと思いました。
Webアプリの開発経験はあるものの、npmパッケージを公開したことはありませんでした。手探りからどのようにリリースまで、残したドキュメントをもとに振り返っていきたいと思います。
これを読んで「OSSを作ってみたいけどどう進めていけばいいか分からない」という人の一助になれば嬉しいです。誰か0から10までの流れを書いててくれないかな〜と思っていたので、きっと誰かには届く気がします。
作ったOSSについて
Realtimelyという、Reactアプリに楽しいリアルタイム要素を簡単に導入できるライブラリを開発しました。 Realtimelyの裏側ではGraphQLとWebsocketでAWSクラウドと通信していますが、プログラマはそれを意識することなくRealtimelyの提供するhooks関数を使うだけでwebsocketベースのリアルタイムな体験を実装することができるようになります。
例えば、下のgifのようにマウスカーソルの位置を共有する機能を10行程度のコードで実現できます。
import { useRealtimeCursor } from 'realtimely';
export default () => {
const { onMouseMove, renderCursors } = useRealtimeCursor()
return (
<div onMouseMove={onMouseMove}>
{renderCursors()}
</div>
)
}
デモページを作っているので、よかったら触ってみてください。
立ち上げからリリースまで
さて、ここからは実際にどのように企画を立ててリリースしていったかを時系列に書いていきたいと思います。
人数 | 1人 |
制作日数 | 18日 |
かかったお金 | AWS無料枠 |
1日にかけた時間 | 平日1時間 休日3時間 |
8/19(開始から0日目) 計画
初日に決めたことは以下になります。
-
ユースケース
- → どのような場面でこのライブラリを使うのかイメージ
-
プロダクトイメージ
- → プログラマがどのようなインターフェイスでこのライブラリを使うかをイメージ
-
アーキテクチャ
- → 何を使って実現するか。
- 今回は可能な限り(楽をするため)ローコードでスケーラブルにしたいという思いがありました。
-
必要なタスク洗い出しとスケジュール
- 何をやらないといけないか列挙
- 何にどれくらい時間がかかりそうか見積もり(正直個人開発で見積もりすることに意味はないですが、どれくらいかかりそうか把握するためにやりました)
今回はかなりプロダクトアウト的な発想でユースケースを検討しましたが、実際には以下の手順でやるのが理想かと思います。
① 実際の開発で困っている課題を考える
② 誰かがすでに実現していないか調べる
③ 実現していたとして、それが本当に便利か、より使いやすいプロダクトイメージがないか考える
OSSは誰にでも公開できますが、すでに実現されていたり、さほど困っていないことをベースにしたものを作って公開しても検索の邪魔になるプロダクトができてしまう恐れがあります。
8/20 (開始から1日目) 競合(?)調査
自分がやりたいことをやれそうなライブラリを調べ、ドキュメントを読み、実際に使ってみました。
そのライブラリは「パフォーマンスの高いリアルタイムアプリ」を作るためにRESTベースで作られたライブラリでした。
高いパフォーマンスが目的だったのでキャッシュ等を利用した状態管理も機能の中に入っておりtoo muchであることや、バックエンドもフルスクラッチで開発しなければならず簡単に使えるものではないことがわかりました。
ここで自分のプロダクトイメージを手軽に使えるようにするところに尖らせるなど方向修正したりしました。
また方向性は違えど、実現することは似ているので参考にできることがたくさんあります。HTTPレスポンスなども見て、どういったデータ構造が良さそうかなどの参考にしました。
8/21-8/26 (開始から6日目) First iteration
「リアルタイムにカーソルの動きを共有する」というスプリントゴールを最初の7日で実装しました。
AWS AppSyncを触る
AppSyncはGraphQLサーバのマネージドサービスです。 DynamoDBのテーブルと接続することで、自動でGraphQLスキーマとリゾルバを生成する機能があります。
今回はローコードでなるべく楽しようと考えていたので、AppSyncを採用してみました。
ノーコード、ローコード系あるあるですが、どうしても限界というかサービスの特徴があり、そこからはみ出すと一気に実装が難しくなります。AppSyncもその例に漏れず、AppSyncの特徴を捉えながら設計をする必要がありました。
触ってみて感じたことはこんな感じ。
- 自分で作ってないものは挙動を理解するのに時間がかかる
- → DynamoDBを手動で更新してもSubscriptionが更新してくれなくてハテナとなった
- → DynamoDBを監視しているわけではなくMutaitonを監視していた
- カスタムロジックを書こうとしたらVTLを書かないといけない
- → 基本的なコードはネットから拾えるが、自分で一からVTLでロジックを書くのはしんどすぎる
- できることの範囲に限界があり、追加要件が走った瞬間に破綻するのではないかという不安から実務で採用は難しそう。
- 自動生成されたリゾルバに勝手にロジックが書かれていてハマる
- → 自動生成系は何が実装されているか分からない
- 不可思議な挙動にも身体で慣れていくしかない。このノウハウにあまり再利用性がない。
- AppSyncでweb socket通信をするためのハンドシェイクプロトコルが独特
- Subscriptionの挙動が奇妙(StackOverflow)
設計する
「リアルタイムにカーソルを移動させる」を実装させるために必要なAPIとフロントでの処理を書き出し、実現できそうか検討していきました。この辺は「実現したいこと」と「AppSyncでできること」を両睨みして考えていく形でした。
ユーザがURL(Host + Path)にアクセス
誰かが入室したら通知(onCreateRealtimeUser)
誰かの所在更新を通知(onUpdateRealtimeUser) → 所在が古いものはローカルで削除する
そのURLへアクセスしていることを報告(CreateRealtimeUser)
カーソルの移動に合わせて位置を報告(createRealtimeCursor, updateRealtimeCursor)
他の人がカーソル移動したら通知(onUpdateRealtimeCursor)
自分が存在していることをpoke (updateRealtimeUser)
DB設計
DynamoDBはキーバリューストアのスケーラブルなNoSQLデータベースです。 DynamoDBのキーはHashKeyとSortKeyがあり、この組み合わせが一意であるようにDB設計を行います。 RealtimeCursorは次のようなスキーマにしました。
HashKey | SortKey |
---|---|
URL#{URL} | UserId#{UserId} |
{URL}と{UserId}には実際の値が入ります。 このキーに対して、マウスのカーソル位置(X,Y)やユーザ情報をAttributeとして保存していきます。
高速実装
僕は実装は何よりもまず最初はスピードだけを意識してやります。
どれだけ汚くてもいいので、とにかくやりたいことができればOKです。
なぜかというと綺麗な設計というのは全てを書き終えてからスクラップアンドビルドをするのが一番効率的だと思っているからです。
最初から何が共通化できるかなんて、僕はわかりません。
リファクタリング
ざっくりと汚いコードでやりたいことが実現できたらリファクタリングをしました。
Webサービスであれば最悪リファクタリングをしなくても動くものを先にリリースするという判断もありかと思います。
今回はOSSライブラリとして使いやすくなければいけないため、責務とインターフェイスをよく考えて再設計しました。
8/27-8/30 (開始から11日目) Second iteration
「ユーザアクションの可視化を実装する(ボタンを押すと他の人にも押されたように見えるとか、テキストを入力するとそれが他の人の画面に見えるとか)」というスプリントゴールに2回目のイテレーションを回します。
リファクタリングされて整理されているのと、おおよそAppSyncで何ができるかわかっているのでスムーズに開発が進みました。
8/31 (開始から12日目) デモページを作る
開発に使っていたNext.jsプロジェクトを多少整理してそのままデプロイします。
Serverless Frameworkを使うことで5分でAWSにデプロイできました。
(デモページ)[https://d2vfno2gco8009.cloudfront.net/]
9/1-9/4 (開発から16日目) ドキュメントを作る
docusaurusというライブラリを使ってドキュメントを作成しました。docusaurusはfacebookがメンテしており、めちゃくちゃよくできてます。
まず、大項目で書くことを列挙してからゴリゴリと書いていきました。
書くことを考える
- Introduction
-
Getting Started
- Installation
- Demo
- Realtime Cursor
- Realtime User Presence
- Realtime User Action
-
API Guides
- useXXX
-
How it works
- Architecture
- Self hosted Backend
- RoadMap
ゴリゴリ書く
ゴリゴリ書きます。一番しんどかったところ。
英語にする
docusaurusにi18n機能があるので、英語のドキュメントも作成しました。
基本的にgoogle translateに翻訳させて、ざっと見直しするだけです。
Github Pagesにデプロイする
これもdocusaurusに機能としてあったので簡単にできました。
その他のタスク
ロゴを作る
これは適当にロゴgeneratorを活用しました。
寄付の窓口を作る
ko-fiを使って寄付の窓口も作ってみました。
Stripeと接続してクレジットカードで寄付ができるようになります。
9/5-6 (開発から18日目) npm公開
いよいよ公開です。詳細なnpmへの公開方法は別の記事を参照ください。今回は荒く書いていきます。
npmアカウントの作成
https://www.npmjs.com/ でアカウントを作成
TypeScriptをコンパイルして.jsファイルにする
tsconfig.jsonを書いてから
tsc -d
yarn publish
npmのログインを求められるのでEmail, Passwordを入力するだけ、、
公開
これでnpmに公開することができました。
公開後の姿
めちゃくちゃ簡単でびびります。npmのライブラリって今まで利用させて頂くばかりで、その土俵に僕なんかがあがっていいのか不安になるような、土俵に汚い土足で乗り上げたような、そんな罪悪感がありました。
デバッグ
意気揚々と別リポジトリでrealtimelyをインストールして使ってみると、エラーが出て使えない・・・
この辺のデバッグで1日溶けました。
- ライブラリの依存関係
- CSS読み込めないエラー
- 依存しているライブラリのエラー
これから
公開へのスピード優先で開発を進めたので、現在は到底Production Readyなライブラリになっていません。あくまでもPoCレベルで、アプリにリアルタイム要素を入れるとどんな感じになるのか確かめて頂く用途で使っていただければと思います。
これからはProduction Readyにするための課題の解決と、より使いやすい機能の開発を細々とやっていこうと思います。温かい目で見守ってください。