Table of Contents
趣味でブログサイトを作りました
この度、ブログサイトをリニューアルしたので、第一弾はブログサイト(このサイトです)の開発日記とします。
仕様
- ブログサイトを使う人
- ブログ記事の著者(私自身)
- 私の発信拠点
- ブログ記事の読者(インターネットにアクセス可能な全ての人)
- ブログ記事の著者(私自身)
- ブログ記事
- タイトル、本文、公開日、タグを持つ。
- タグはブログ記事を集約するためのもの(例
プログラミング
というタグをプログラミングに関する記事へ付与)。 - 簡易的な記事検索機能(タグや公開日による絞り込み)がある。
- ブログ記事の書き方
- ブログ記事を Markdown によって記述可能。
- ブログ記事の著者は管理画面を用いてブラウザ上でブログ記事を作成可能。
上記を実現するための代表的な画面一覧。
- ブログ記事の編集画面
- SSR 画面に SPA アプリケーション(React)を埋め込み、記事の編集体験を向上させた。Basic 認証。
- ブログ記事の閲覧画面
- SEO のことを考慮し、SSR のみで作った。
- Markdown を HTML へ変換した後の HTML が表示される。
- ブログ記事の一覧画面
- トップ画面
どう作った(一言で)。
- インフラは GCP。
- Cloud DNS -> External load balancer -> Cloud run -> CloudSQL(MySQL) or Cloud storage
- 個人用のブログサイトでこんな構成にする必要はないのだが、GCP の勉強を兼ねて、ということで、「一般的な Web アプリケーションを作るならこうする」という構成を目指した。
- Terraform により完全に IaC 化。
- Cloud DNS -> External load balancer -> Cloud run -> CloudSQL(MySQL) or Cloud storage
- フロントエンドは SSR。記事の編集画面のみ React。
- 最初、React 使わないで(TypeScript と DomAPI だけで)実装してみたけど、すぐに破綻した。Dom 操作を React 使わずに実装することは、やばいっす・・・。
- React を部分的に SSR のページに対して埋め込む、みたいなことをやった。これが結構便利。普通にうまく実装できた気がする。
- バックエンドは Go。
- Clean architecture を採用
- (小声)あまり考えずに脳死でこのアーキテクチャを採用している。
- Clean architecture を採用
断念した機能
(独身時代と違って、本業と育児の両方があると時間がないっすね・・・)
- アカウント作成機能。初めは、アカウントを持つ人が記事を書けるようにしようと考えていた。
- Content editable を用いた Markdown エディタ(Qiita みたいなやつ)。トライしたが、想像以上に難航したため、諦めた。Content editable な HTML ダグへペーストした時の挙動を制御することが難しい。Qiita などではどうやってるんだろう?
- 画像をアップロードした後の非同期による画像加工処理の実装。実装の面白さはあるが、必要性を感じられず、実装を途中でやめた。
ソースコード
なぜ作ったか?
- Go と GCP を使って何かを作りたかったから。クライアントサイドはなんでも良かった。できれば全てを SSR で実装したかったが、記事の編集体験を向上させるためにはどうしてもクライアントサイドの実装が必要だったため、React を部分的に利用した。
- 題材はなんでも良かったけど、長期的に使われるものを作りたかった。誰かに使われて、長期的に運用が必要となる「何か」を考えたときに浮かんだのがブログサイト。ブログサイトであれば、少なくとも自分 1 人は使う。発信し続けることで、長期的にも使われる。
- 本業で 0 から開発する機会がないから。0 から開発やるため。やりたい。マジで。
インフラ構成図
シンプルな構成だけど、ボトルネック箇所をなるべく無くすように心がけた。MySQL 以外は水平スケールが可能(MySQL は常時運用コストがかかるので最小構成で構築したため、インスタンス 1 つだけで動いている)。
今回、趣味開発用の Google workspace を作った。続いてリソース階層にあるような Organization、Folder、Project を作った。
- Organization tach.dev
- 趣味開発用の組織を作った。今回はブログサイトを作ったけど、今後も何かネタがあれば Web サービスを作るかもしれないからだ。
- Organization を作るためには Google workspace の有料契約が必要となる。最安のプランが年間 1 万円以下だったので、契約した。趣味にかかる必要経費だ。
- Project base
- 組織横断的に使用されるリソースを作るためのプロジェクト。
- 組織配下の Folder や Project を作るための Terraform の state ファイルを管理するための Google storage バケットがここにある。今のところそれだけ。
- リソース一覧 Terraform
- Google storage
- Folder products
- 全ての Web サービスフォルダーが格納されるフォルダー。
- 基本的に、1 つの Web サービスにつき、1 つのフォルダーが対応する。Web サービスフォルダーの配下に、環境毎にプロジェクトが作られる。環境の種類は 3 つ。stg と prd、それと common。common は、stg と prd で共用のリソースを格納するためのプロジェクトだ。
- Folder blog
- Web サービスであるブログサイトのフォルダー。
- Project blog-{stg,prd} Terraform
- Web サービスであるブログサイトのプロジェクト。
- リソース一覧
- Google cloud loadbalancing (External global load balancer)
- Google cloud run
- Google storage
- Google cloud build
- Google artifact registry
- Google IAM
- Folder products-common
- プロダクト横断的に使用されるリソース。
- 例えば、Cloud DNS や SQL インスタンスがそれにあたる。
- SQL インスタンスは、お金を節約するために共用としている。
- Project products-common-common
- リソース一覧 Terraform
- Google cloud DNS
- Google cloud sql
- Google IAM
- リソース一覧 Terraform
RDB のテーブル
特筆すべきことは少ない。オーソドックスなブログサイトのテーブル設計。 search_index は、検索インデックス用のテーブル。このテーブルを用いて記事検索機能を提供する。後々に Elasticsearch へ代替されることを意識したテーブル設計とした(けど、代替される日は来ないと思う)。 一部の機能において、transaction を未実装である(まぁ未実装でも困らんし、ええんちゃう的な・・・)。
クリーンアーキテクチャ
- Presenter 層
- https://github.com/suzuito/sandbox2-go/tree/cec117705632fb097877855719f5e3eff5336681/blog2/internal/web
- 責務
- HTTP リクエストのリクエストパラメータ検証。
- HTTP リクエストのリクエストを変換し、Usecase 層へ渡す。
- Usecase 層の返り値を DTO 構造体として受け取り、HTTP レスポンスを返す。
- ページを表示するためのエンドポイントであれば、HTML を生成する。Restful API のエンドポイントであれば JSON を生成する。
- Usecase 層
- usecase パッケージ
- https://github.com/suzuito/sandbox2-go/tree/cec117705632fb097877855719f5e3eff5336681/blog2/internal/usecase
- 責務
- 1 つのエンドポイント毎に 1 つの関数。エンドポイントの処理をこの層に記述する。
- service パッケージ(後述)の配下の関数を呼ぶことでビジネスロジックを実現する。
- ビジネスロジックの処理結果を DTO 構造体として返す。
- service パッケージ
- https://github.com/suzuito/sandbox2-go/tree/cec117705632fb097877855719f5e3eff5336681/blog2/internal/usecase/internal/service
- 責務
- 実際にビジネスロジックを記述する層。Repository インターフェースなどを用いてビジネスロジックを記述する。
- usecase パッケージ
- Infra 層
- https://github.com/suzuito/sandbox2-go/tree/cec117705632fb097877855719f5e3eff5336681/blog2/internal/infra
- 責務
- 外部通信の具体をここに記述する。主にはレポジトリの実装。
- その他
- markdown2html パッケージ
- https://github.com/suzuito/sandbox2-go/tree/cec117705632fb097877855719f5e3eff5336681/blog2/internal/markdown2html
- 責務
- Markdown を HTML へ変換する処理を担う。
- 現在、goldmark というライブラリを用いて変換処理を実装しているが、後々の置き換えを可能とするためにパッケージとして抽出してある。
- articlefile パッケージ
- https://github.com/suzuito/sandbox2-go/tree/cec117705632fb097877855719f5e3eff5336681/blog2/internal/procs/articlefile
- 責務
- 記事に添付するファイルのサムネイル生成処理を担う。
- ファイルの種類が増えた時にサムネイルし生成処理を簡単に増やせるようにするためにパッケージとして抽出してある(そんな日は来ないのかもしれないが・・・)。
- markdown2html パッケージ
React in SSR ページ (記事編集画面)
React を SSR ページに埋め込んだ。
React 用のソースコードはこちら。 画面毎に js が生成されるようにwebpack.config.jsを作成。← の例では、./src/page_admin_article/main.tsx ファイルをビルドし、page_admin_article.js を生成している。page_admin_article.js は、記事編集画面の SPA で必要となるコードを提供する。もしそれ以外の画面で SPA を実装したい場合は、module.exports.entry を追加することで比較的簡単にできる。
- ページ毎に js を分割することで、js のサイズを小さくできる点もメリット。
記事編集画面
SPA として実装した記事編集画面をお見せできない(この画面は管理者のみアクセス可能であり、今のところ、ブログサイトの管理者は私 1 人)。雰囲気だけでも掴んでもらえればと、スクリーンショットを掲載する。
ブログサイトで最低限必要となる記事の編集機能は、この画面の中で完結する。
- 記事の公開、非公開の制御機能
- ページ上部のドラフトボタンを押下することで制御が可能。
- タイトルの変更機能
- タグの付与機能
- 添付ファイル(画像など)アップロード機能
- Markdown のプレビュー機能
Markdown エディタは単なる textarea なため、若干、記事の書き心地は悪い。でも、プレビュー機能があるので、ぶっちゃけあまり気にならない。
感想
0 から作るっていうのはやっぱり楽しい。仕事ではなかなか味わえない楽しさがある。今回は Google workspace から作った。これからも何かを作り続けたい。あと、可用性のことを考慮しながら GCP のロードバランサー周りについてあれこれ考えるのは楽しい。インフラ寄りのバックエンドエンジニアの醍醐味。
今後
- 記事の編集体験を向上させたい。オートインデント、オートセーブはやっぱり欲しいです。オープンソースな js 製のエディタを導入しようと考えている。自作はハードル高い・・・というか高過ぎ(Content editable くんを手懐けられない)・・・悔。
- Tokyo と Osaka でマルチリージョン化させてみたい。マルチリージョン化を容易にできるインフラ構成にしたはず。
- SEO 対策。検索エンジンから流入という美学。
- UT や E2E テストを作りたい。酒のアテにテストを少しずつ書いていく感じか。誰かに使われて、長期的に運用が必要となる「何か」を。
- 記事に添付する画像配信用の CDN 作る。まぁお金との相談かな。趣味開発にどれだけ金をかけるべきか。