趣味でブログサイトを作りました
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作る。まぁお金との相談かな。趣味開発にどれだけ金をかけるべきか。