作成の際に検討したことや苦労話を書いておきます。
昔から、人がプログラムでなにかを作っているのをみて、彼らはなにも苦労せず作成できているように感じていました(絶対そんなこと無いと思うんですけどね。皆さんそれぞれ苦労しながら作成していると思うので。誤解による所産です)。
愚かな身の感想として、「みんなぐっとガッツポーズしただけで作品ができている」様に感じていた、というわけです。
しかし、自分もいろいろ作るようになり、さらに長じて人の苦労を知ると、「裏で苦労している部分はあるが、表に出していないだけ」というのが分かってきました。
分かってはきましたが、その苦労したところ、それから作成する過程をぜひとも知りたかったんですね。
どう悩んで作ってきたのか、悩んで止まったときはどの様に解決しているのか、等々...
「作ります」 -> 「できました」の間の飛躍の中身が知りたかったというわけです。
そういった思いがあったんですが、そういった物を記載するかどうかは個人の自由です。
書かれることを期待することがナンセンスだったのだと、いまでは思っています。
しかし、人に期待していたのだから、自分はせめてなにか作成過程を残すかなーと思って書いたのが、本文書になります。
参考にするなり、読んで笑うなりしてもらえればよいかと思います。
文章稚拙なため、参考にならない場合は申し訳ない。
なお、今回作成したものがRecursionCSのバックエンドプロジェクトの課題の一つのため、RecursionCSをやっているユーザーには多少でも参考になるところがあるかもしれません。
今回作成したものの要件は以下になります。
- スニペットのアップロード
- テキストエリアにテキストやコードを貼り付ける
- プログラミング言語ごとに構文ハイライトする
- ユーザーが内容を送信すると、スニペット用のURL(一意)が生成される
- フォーマットは
https://{domain}/{path}/{unique-string}
- フォーマットは
- スニペットの閲覧
- 一意のURLにアクセスしてスニペットを閲覧できる
- コードハイライトあり
- スニペットの有効期限設定
- スニペットの有効期限が設定できる(例: 10分、1時間、1日、永続)
- 期限切れになったスニペットは自動的に削除され、「Expired Snippet」とメッセージを表示する
- データストレージ
- ユーザーからの送信されるデータは、厳格に検証とサニタイズが行われる必要がある
- SQLインジェクションを防ぐために、スニペットは安全に保存される
- フロントエンドインタフェース
- シンプルで使いやすいインタフェース
- スニペット成功して送信されると、内容にアクセスできる一意のURLが生成され、ユーザーに表示する
- エラーハンドリング
- 大量のテキストやコード、または、サポートされていない文字が送信された場合でも、適切に処理してエラーメッセージを表示する
- ウェブインタフェース
- HTML/CSSを利用
- JavaScriptも使用
- monacoエディタを使用
- バックエンド
- サーバーサイドは静的型付けが可能なOOPの言語。例ではPHP8.0
- 一意のURL生成にはhash()のようなハッシュ関数を用いる
- データベース
- MySQLを使用
- ミドルウェア
- マイグレーション管理システムを使用
- DBとの接続にMySQLWrapperを使用する
- デプロイメント
- サービスはユーザーが簡単に記憶できるドメインやサブドメインで公開する必要がある
- サービスが常に利用可能となるようにする(使えない時間を極力少なくする)
- Gitコマンドを実行するだけで、コードの更新と同期が自動で行える必要がある
- パフォーマンス
- スニペットを効率よく取得して、ユーザーが迅速に閲覧できるようにする必要がある
- ページの読み込みが極端に遅くなることなく、速やかに構文ハイライトを表示できるようにする
- スケーラビリティ
- 大量のスニペットが送信されても、それらをスムーズに処理できるシステムを確立する必要がある
- セキュリティ
- スニペットが安全に保存され、不正アクセスを防ぐ必要がある
- 安全な接続とデータの暗号化を保証するために、HTTPSを採用する必要がある
要件は以上ですが、すべての要件は満たしていません。
言語を変えたり、意図的によりよいと思ったものに変更したものがあるためです。
故に、課題としては百点満点で実装できたわけではないことを、ここに明言しておきます。
今見返すと取りこぼしたところもありそうだしね...(目逸らし)
さて、ここから苦労した点や作成時の考え方などについて記載します。
本来、バックエンドプロジェクトで想定されているのは以下のものでした。
- php
- JavaScript
ただ、プロジェクト側で静的型付け言語であればいいといった記載があったため(あったかな? ...あったと思う)、趣味で以下の言語を選択しました。
バックエンドはRust。 趣味で選定。
本来はバックエンドプロジェクトおわるまではRustの学習は我慢する予定でしたが、バックエンドプロジェクト自体が時間がかかる内容であることもあり、我慢できずRustを使用することにしました。 というよりも、「やりたい言語があるのに自分を縛り続けるのも意味がわからないな」といったところが実情でしょうか。 どうせ苦労するなら好きな言語使っちまえ、ということですね。
やっぱり好きなものやるほうが健康に良いですね。
しかし、Rustを選定することで苦労が生じました。それについては後述します。
フロントエンドはTypeScript + Reactを使っています。
こちらはもともとReactプロジェクト等で勉強していたこと、それからjsx記法、コンポーネント化の機能を気に入っていたため使用しています。 一つ前の静的サイトを作成する課題で使用していたので、そのまま続投した形です。
なお、その時作ったサイトはこちらになります。
Rustを選定したことによって生じた苦労について。
もともとはphpで進めるプロジェクトであり、学習中のサンプルコードももちろんphpで書かれていました。 しかし、Rustを選んだことで、そのサンプルは使えません。 そのため、サンプルコードもRustで自力実装する必要が生じました。
しかも、Rustとphpの言語の特徴の差異のため、この自力実装にかなり悩まされました。 何に悩まされたかというと、
- サンプルコードはphpによりOOPを使用したコードでした。RustはOOPも実装は可能ですが、せっかくならよりRustっぽく実装したいと思っていたので、Rustのパラダイムに変換しながら実装する必要が生じました。これは自分で背負った苦労です。
- サンプルコードはphpのスクリプト言語の側面を最大限に利用していました。Rustではコンパイルする必要があるため、そこの違いをどの様に実装するべきか、といったところをかなり悩み、なかなか骨が折れました。苦しいです。評価してください。
上記の点、それからRust自体のキャッチアップを含めて、結局作成志してから作り上げるまで9ヶ月かかってしまいました。
もちろん間でwebsiteを作ったり他のプロダクト作成をしていたため、そのあたりの時間を差っ引く必要はありますが...
さすがに時間かけすぎでしょ...
わりぃ...やっぱ...つれぇわ......
まあ無事に完成したので無問題です。 時間かかったことは特に気にしていません。後悔もしていません。本当です。信じてください(曇りなき眼)。
冗談はさておき、苦労はしましたが、結果的には良かったと考えているのが以下の点です。
- RustでCLIツールを作るところから始めたので、Rustの学習がスムーズだった。
- また、たくさんのツールを作る必要が生じたため、経験値も積めた。
- 最終的にAPIサーバをRustで作成できたので、自信につながった。
- Rust触るの楽しい。
さて、phpからRustに変えることで自作したツールは以下のものです。
個別にリポジトリを切っているので、興味があればご覧ください。
GitHub - kip2/consowrap: It is a simple console wrapper application made in Rust.
コマンドツールをまとめて一括で扱えるようにするためのCLIツール。
GitHub - kip2/sqcr: This is a simple application for executing SQL from the console.
単純にsqlファイルのSQLを実行できるようにしたコマンド。当初必要かなと考えて作ったが、結局不要になったため使っていない。
GitHub - kip2/db-wipe: A simple command-line tool for deleting databases in MySQL.
MySQLのバックアップを行うためのツール。MySQLは使用しないことになったため、作ったが使っていないツール。
GitHub - kip2/migrate: This is a simple migration application that can be executed in the console.
マイグレーション用のツール。PostgreSQLとMySQLに対応したマイグレーションツール。 MySQLが要件にあったが、マイグレーションの仕様を誤解していたため、DDL文によるトランザクションを回避する必要があると誤解し、PostgreSQLでも使用できるようにしている。
マイグレーション用のツールで、途中PostgreSQLに変更しようか検討したときに作成。
上のmigrateに機能が統合されたため、こちらは作ったが使っていない。
シーダー用のツール。一番最後に作成。
このころには使用するDBが決定していたため、PostgreSQLのみに対応している。
今回利用しているサーバは以下になります
- バックエンドサーバ:APIを配置するサーバ。
- フロントエンドサーバ:クライアントに配布する画面機能を配置するサーバ。
- DBサーバ:DBを配置するサーバ。
プロジェクトの流れに従うと、1つのサーバ上に上記3つを乗せることになると思います。
とはいえ、冒険してはいけないわけではないと思うので、冒険して個別のサーバにわけました。
また、冒険したのは以下の理由もあったからです。
- 一つのインスタンス(AWS)に乗せるとスペックを考慮する必要があります。特に、今後バックエンドプロジェクトを進めると他のプロダクトも乗せる必要があるため、それによる懸念がありました。
- お金をあまりかけたくなかったことがあります。一点目と関連する内容ですが、インスタンスの数を増やしたりスペックを増強するとお金がかかります。公開するサービスの永続性のためにもできるだけお金をかけずに継続したかったのです。
- 単純に冒険したかったことがあります。いろんな技術触りたい。技術触るの楽しい。
Rustをバックエンドに選定したことによる苦労等は言語の欄で語っているため、こちらでは省略します。
その他の覚えてる範囲のことを書きます。
今回のプロダクトでは、要件上、API側で一意のハッシュを生成する必要がありました。
いろいろ実現手段はあるかと思いますが、最初、一意性があり衝突性を気にしなくて良いと聞いたため、Blake3を使おうと選定しました。
しかし実際に使ってみると、今回のプロダクトには向かない点がみつかりました。
生成されるハッシュが64文字ありURLにするには長すぎる、という点です。
さすがにURLにするのに64文字は長すぎのかなと思い、検討した結果、UUID + sqidsクレートを採用しました。
Short Unique IDs in Rust · Sqids
sqidsを使うと一意性を失わずにハッシュを短くできるそうです。
難しいことはわかりませんが、どうやらそうらしいです。
さて、実現方式としては、UUIDを生成しsqidsクレートを使用して62進数エンコード、という形で実現しています。
これで、64 -> 24文字までハッシュを短くできました。
バックエンド用のサーバにAPIを配置するにあたり行ったことは以下になります。
- ビルド用にdockerfileを用意
- デプロイごとにビルドとディレクトリの配置変更してくれるように、シェルスクリプトを用意
- APIサーバをデーモンとして設定。systemdを用いて管理。
なぜDockerfileか、というと、単純にサーバの環境を汚したくなかったからです。
ビルド用の環境を、Dockerfileを用いることで、ポータビリティ性を持たせています。
もしサーバを移管する必要があっても安心。
フロントエンドで覚えてる範囲のことを書きます。
いままではフロントのUIも一から作ってきたが、自サイトを作る際に一から作る苦労を嫌と言うほど知ってしまったため(作成に160時間くらいかけてしまった)、UIライブラリを使おうと思いました。
また、これは個人的な思いですが、OSS活動もいつか参加したかったということがあります。
YamadaUIは定期的に参加の呼びかけを行っており、更に、参加用のドキュメントもあり、OSS参加に最適に思えました。
しかし、YamadaUIを使ったこともないのに参加するのもおかしい話なので、一度ちゃんと使ってみたかったのです。
そこで、OSS活動の参加まで見据えて、YamadaUIを使うことに決定。
あれだけ苦労していたUIがするすると簡単に実装できるのはかなり楽しかったです。
しかもドキュメントが完全日本語対応しているので、困った場合も公式ドキュメントがある、という安心感があります。
みんなもぜひ使ってみよう。
コードエディターのサイトのため、エディターの部分を担うライブラリが必要です。
もともとの要件ではmonacoが推奨されていました。
しかし、いろいろ探したところ、CodeMirrorという便利なライブラリがあることを知ったため、こちらを使用するようにしました。
コードハイライトやテーマが充実しているため、おかげてかなりの言語をサポートしたり、いろんなバリエーションのテーマが使えるようになりました。
しかし、公式ドキュメントが若干読みにくいと思う...自分だけかな?
一部のデザインについて、UIVerseからお借りました。
Switch by aymenthedeveloper made with CSS | Uiverse.io
Button by McHaXYT made with CSS | Uiverse.io
Button by xopc333 made with CSS | Uiverse.io
Loader by jeremyssocial made with CSS | Uiverse.io
Button by gharsh11032000 made with CSS | Uiverse.io
フォントに関してはGoogle Fontsからいくつかお借りしています。
サイトのアイコンはこちらからお借りしています。
セキュリティ関係で覚えてる範囲のことを書きます。
バックエンドとしてAPIサーバを立てています。
何も設定しないままではあらゆる場所からのアクセスが可能となってしまうため(対策をしないと画面機能を通さずにAPIにアクセスできる可能性があります。API機能は公開していないので防ぐ必要がある、ということです。)、アクセスするオリジンを制限することでクロスオリジンを対策しています。
同じサーバ上に配置していれば気にしなくていい部分かと思いますが、今回は冒険でフロントエンドとバックエンドを別のサーバに配置したため、このような措置の必要性が発生しています。
.envに設定値を設定することで、セキュリティに配慮しています。
これしか知らないからこれでやってるだけなので、他にいい方法あったら教えて下さい(他力本願)。
もともと、ドメインはお名前.comで取得していましたが、今回、Cloudflareを使用するに当たり、Cloudflareに移管しています。
DNSの移管は移管で面倒も多い作業でしたが、なかなか楽しい作業でした。
Cloudflareは初めて触りましたが、なかなかいいもんですね。
管理画面は使いやすいし、DNS、静的サイトのデプロイサーバと、一気通貫で一つのサービスで管理できるのが魅力です。
今回使った以外のサービスや機能もあるため、今後使い込んでいきたいなーと思っています。
作成の際に参考にさせていただいた資料等を記載しておきます。
世の中に技術記事を書いてくださる方がいるからこそ、私のような愚鈍の性質でも技術にキャッチアップすることができます。
技術記事を書いている方に最大限の感謝と敬意を評して、この場を借りてお礼を述べさせていただきます。
いつもありがとうございます。
Cloudflare Registrarでdevドメインを取得した
お名前.com から Cloudflare Registrar にドメイン移管した話
Cloudflareにドメインを移管してみた | DevelopersIO
Cloudflare Registrarでdevドメインを取得した
個人開発のWebサービスをCloudflareに載せてみた【無料でここまでできる】
AbortController - Web API | MDN
React で初期化時に 1 回だけ処理を実行したいときの書き方
なぜ非同期処理? Node.jsの実装から読み解く Fetch API の response.json()
バグを減らす第一歩は、ちゃんと名前を呼んであげること(名前付き引数を使おう)
【初心者でもわかる】cssで使われる透明3種類の使い方 #CSS - Qiita
cubic-bezier を知る。 #CSS - Qiita
ReactでCodemirror6を使ってみた - Qiita
React CodeMirror - CodeMirror component for React.
CodeMirror v6によるZennのMarkdownエディタの作り方
新しくなったCodeMirror v6で遊ぼう! - TECHSCORE BLOG
How to use legacy-modes in CodeMirror6 - v6 - discuss.CodeMirror
[Next.js] SSR と React Server Components の相違点を理解する
SSRとReact Server Components の違いをレスポンスから考える - Qiita
htmxとは何なのか? その背景にある思想について - Qiita
【2024年】React Router & TanStack Router比較
TanStack Router(& Query)はSPA開発で求めていたものだった✨【Reactのルーティングとデータ取得】
Go、Rust、Pythonで実装したAPIサーバーの負荷試験比較 #Postman - Qiita
RustでWeb APIを作る際のエラーハンドリング - CADDi Tech Blog
Pavex – Rust API構築のための新しいWebフレームワーク | DevelopersIO
E2E テストの実装方法|Rust で MVP な Web API サーバを開発する方法
systemdでユニットファイルを作ってサービス化してみる #初心者 - Qiita
hashアルゴリズムとハッシュ値の長さ一覧(+ハッシュ関数の基本と応用) #ブロックチェーン - Qiita
【Hash関数】できるだけ衝突率を上げずにIDを短くしたい #JavaScript - Qiita
Hashidsを使って短いハッシュ値を生成する - Qiita
【クラウドDB比較】無料枠で提供されるサービスレベル - Qiita
フルマネージドサーバーレスPostgres「Neon」を触ってみた
PlanetScaleの無料プランがなくなるので、NeonとTiDBを試してみた - wheatandcatの開発ブログ
安価かつスケーラブルなサーバレスバックエンドプラットフォーム ~KoyebとNeonのススメ~
Bun & TypeScriptでバックエンド開発:サーバーレスDB「Neon」の基本的な使い方 | Go-Tech Blog
SQL Training 2021 - Speaker Deck
CockroachDB Serverlessから学んだこと|昭和のオッサン
CockroachDB はどのくらい「しぶとい」のか? / How tough is CockroachDB? - Speaker Deck
Cockroach DBをsingle-node clusterで動かすdocker-compose最小構成 - Qiita