Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# see https://www.npmjs.com/package/@hono/oidc-auth
OIDC_AUTH_SECRET="<YOUR_AUTH_SECRET>"
OIDC_ISSUER="<YOUR_OIDC_ISSUER>"
OIDC_CLIENT_ID="<YOUR_OIDC_CLIENT_ID>"
OIDC_CLIENT_SECRET="<YOUR_OIDC_CLIENT_SECRET>"
79 changes: 79 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Deploy

on:
push:
branches:
- main

jobs:
terraform:
name: Terraform Apply
runs-on: ubuntu-latest
defaults:
run:
working-directory: terraform

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

- name: Create Terraform Backend Config
run: |
cat > terraform.tfbackend << EOF
access_key = "${{ secrets.R2_ACCESS_KEY }}"
secret_key = "${{ secrets.R2_SECRET_KEY }}"
endpoints = { s3 = "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com" }
EOF

- name: Create Terraform Variables File
run: |
cat > terraform.tfvars << EOF
cloudflare_api_token = "${{ secrets.CLOUDFLARE_API_TOKEN }}"
cloudflare_account_id = "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}"
auth0_client_id = "${{ secrets.AUTH0_CLIENT_ID }}"
auth0_client_secret = "${{ secrets.AUTH0_CLIENT_SECRET }}"
EOF

- name: Terraform Init
run: terraform init -backend-config=terraform.tfbackend

- name: Terraform Plan
run: terraform plan

- name: Terraform Apply
run: terraform apply -auto-approve

wrangler-deploy:
name: Wrangler Deploy
runs-on: ubuntu-latest
needs: terraform

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Install dependencies
run: bun install

- name: Build
run: bun run build

- name: Apply D1 Migrations
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: d1 migrations apply cloudflare-demo --remote

- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
34 changes: 34 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# prod
dist/

# dev
.hono/
.wrangler/
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# deps
node_modules/

# env
.env
.env.production
.dev.vars

# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# misc
.DS_Store
116 changes: 116 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Cloudflare Demo

行灯職人への道の新しい構成を試すためのリポジトリ

# 環境構築

## インフラの構築 (初回のみ; 灯雪会アカウントを使う場合は構築済み)

Cloudflare にアクセスし、R2 のアクセストークンおよび Cloudflare D1 の権限があるアクセストークンを取得してください。
Auth0 にアクセスし、Terraform 用のアプリケーションの client ID と client secret を取得してください。

```
cd terraform
cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars
cp terraform.tfbackend.example terraform.tfbackend
$EDITOR terraform.tfbackend
terraform init
terraform plan
terraform apply
```

## ローカル開発

`terraform output` で出力された値を `.dev.vars` に設定します。
OIDC_AUTH_SECRET はランダムな値を生成します。

```
cp .dev.vars.example .dev.vars
$EDITOR .dev.vars
bun i
bun run dev
# => localhost:5173
bun run build
bun run preview
# => localhost:8787
```

## DB マイグレーション

ローカルの D1 がマイグレーションされる

```
bun run db:generate
bun run db:apply
```

## デプロイ

```
bun run build
bun run deploy
```

# プロジェクトの構成

主にドメインとその外側で分かれている。だいたい DDD に乗ってるつもり。

- `/domain`
- ドメインモデルとビジネスロジック
- 一番大事なやつ
- ルール: I/O や外部サービスには依存せず純粋に保つ
- `/domain/values`
- いわゆる Value Object
- ルール: immutable にする
- `/domain/entities`
- いわゆるエンティティと集約
- ルール: id を持ち、id で同一か判断される。mutable。集約されている場合は集約ルートからしか触らないようにする。values にのみ依存
- 集約にするかどうか: 各エンティティの状態やライフサイクル間の整合性が大事な場合にのみ集約にする
- `/domain/interfaces`
- インフラ層のインタフェース
- ルール: 具体的なサービスには依存しない。values と entities にのみ依存
- `/domain/usecases`
- いわゆるアプリケーションサービスとドメインサービスがごっちゃになった層
- ルール: 複数エンティティにまたがる操作、リポジトリ層を使う操作などをユースケース毎に書く。values と entities と interfaces にのみ依存
- `/infra`
- DB や外部サービスとのやり取りを行う具体的な実装
- サービス名ごとにディレクトリを掘っている
- domain/interfaces を実装する
- ルール: domain と各サービスや DB の実装に依存
- `/app`
- HonoX を使用したルーティングとコンポーネントでアプリケーションのエントリポイント
- (HonoX のデフォルトの名前が app なだけでアプリケーション層を表しているわけではない)
- ルール
- Cloudflare Workers で動くという知識を使っていい

本当は `src` ディレクトリを作ってその下に各モジュールを入れたかったが、HonoX のデフォルトが `app` ディレクトリなのでリポジトリ直下に `domain` や `infra` ディレクトリを置くことにしている

# その他

## エンティティの ID について

- ULID を使用する
- auto_increment は以下の理由により使わない
- エンティティの生成時に ID が事前に必要
- DDD 的にはエンティティを作ってから永続化するのが推奨らしい
- エンティティの作成メソッドにバリデーションなどを含められる
- Cloudflare D1 はトランザクションをサポートしていないため、sqlite を使って ID を取得しつつそれを insert ということがしにくい (衝突の可能性がある)
- Durable Object で ID を生成することもできるが、実装コスト・パフォーマンスの観点から避けたい
- UUIDv4 や CUID2 は以下の理由により使わない
- 完全ランダムだと DB のパフォーマンスに影響する
- 例えば画像のメタデータを DB で管理することになった場合、10 万単位のレコード数になりうる
- 画像を一括アップロードした場合、ミリ秒以内に投入した画像を投入順で取得したいことがあり、その場合 ULID のほうが便利 (ランダム ID でも工夫すれば可能だが、ULID のほうが簡単)
- どうしてもランダムな値にしたい場合はそこだけ CUID などを使うようにする

## 認証

- Auth0 を使用する
- が、アプリ側に明示的に Auth0 に依存しているものはなく、環境変数を変えれば他の認証サービスでも動く (OIDC に準拠していれば)
- /dashboard 以下にアクセスするには Auth0 のログインかつ DB へのユーザ情報の登録が必要
- 未ログインの場合は Auth0 のログインページにリダイレクトする
- Auth0 ログインページではサインアップも可能
- Auth0 でログインしても DB にユーザが保存されていない場合 (sign up 直後や登録処理で離脱した場合)、ユーザ情報登録ページにリダイレクトする
- そこで privacy policy や terms of service に同意してもらう
- username や display_name、卒業期を入力してもらう
- 最低限のユーザー情報 (email, password) 以外は DB 側で管理
3 changes: 3 additions & 0 deletions app/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createClient } from 'honox/client';

createClient();
Loading