Skip to content

島島(DaoDao)技術架構全覽:Monorepo、多語言後端與 AI 推薦系統

2026年3月12日 1 分鐘
TL;DR Next.js + Expo 前端、Node.js + Python 雙後端、PostgreSQL + Redis 核心架構,加上社交通知系統與 LLM 推薦引擎,島島如何用現代技術棧打造學習社群平台。

島島(DaoDao)是一個學習平台,讓使用者設定目標、追蹤每日實踐、建立學習社群。它的技術架構比大多數同規模的產品複雜:前端是 Turborepo monorepo 管三個 app,後端拆成 Node.js 和 Python 兩個服務,資料庫用了四種。這篇拆解各層的設計邏輯,以及這些選擇背後的取捨。

Monorepo 架構設計

前端用 Turborepo 管理整個 monorepo,底層是 pnpm workspaces。

daodao-f2e/
├── apps/
│   ├── website/        # Next.js,port 3000(行銷 / 落地頁)
│   ├── product/        # Next.js,port 3001(主應用)
│   └── mobile/         # Expo / React Native
└── packages/
    ├── shared/         # 共用型別、utils
    ├── ui/             # shadcn/ui 元件庫
    ├── i18n/           # 多語言
    ├── api/            # OpenAPI client(自動生成)
    └── features/quiz/  # 測驗功能模組

websiteproduct 分開是很常見的決策:行銷頁和應用程式的 deploy 頻率、快取策略、SEO 需求都不同,分開可以獨立優化,但共用 packages/ui 保持視覺一致。

Turborepo 的 pipeline 設定讓 buildlinttype-check 可以平行執行,只有真正有相依關係的 task 才會等待——product 的 build 不需要等 website 跑完,但都需要等 packages/ 先 build。

前端技術選型

三個 app 的技術底層:

  • website / product:Next.js 15 App Router + React 19,TypeScript 5.7+
  • mobile:Expo + React Native(跨 iOS / Android)
  • UI:shadcn/ui + TailwindCSS,元件放在 packages/ui,所有 app 共用
  • Linter / FormatterBiome,取代 ESLint + Prettier

Biome 這個選擇值得特別說一下。它用 Rust 寫的,lint + format 合一,速度比 ESLint + Prettier 的組合快 10-20 倍,設定也簡單很多。對 monorepo 來說,加速效果更明顯——原本三個 app 分別跑 ESLint 的時間可以減少到一個 Biome pass 完成。缺點是部分 ESLint plugin 生態還沒有對應的 Biome rule,但對多數專案來說這不是問題。

Next.js 15 + React 19 的組合帶來 Server Components 和 use cache directive,可以細粒度控制哪些資料在伺服器端快取、哪些需要 client-side fetch,比 Next.js 13/14 的 fetch cache 選項更直覺。

後端架構分層

Node.js 後端(daodao-server)用 Express.js + TypeScript,分層清楚:

routes → controllers → services → Prisma ORM

           middleware(auth、rate limit、validation)

每一層職責分明:routes 只管路徑對應和 middleware 掛載,controllers 處理 HTTP request/response,services 放業務邏輯(不知道 HTTP 存在),services 內部直接透過 Prisma client 操作資料庫,沒有額外的 repository 抽象層——對目前的團隊規模來說,少一層間接層反而更直覺。

所有 API 回應遵循統一格式:

{
  success: boolean,
  data: T | null,
  timestamp: string,
  meta?: { page?: number, total?: number, ... }
}

這讓前端的 API client 可以統一處理錯誤,不需要每個 endpoint 各自判斷回應結構。

身份驗證走 JWT + Passport.js,支援 Google OAuth。設計上有一個細節:所有對外暴露的 ID 都使用 External UUID(而不是資料庫的自增 ID),防止攻擊者透過猜測 ID 枚舉資源——/api/posts/1/api/posts/2 這種 URL 是資安漏洞,UUID 格式的 ID 讓猜測變得不可行。

資料驗證全面使用 Zod,schema 在 service 層定義,同時作為 TypeScript 型別來源和 runtime validation——型別推斷和驗證邏輯不需要寫兩份。

多資料庫策略

島島的資料層以 PostgreSQL + Redis 為核心,各有明確職責:

PostgreSQL(主資料庫,透過 Prisma ORM) 所有結構化資料的單一來源:使用者、目標、實踐記錄、社群關係、貼文、留言。Prisma 提供型別安全的查詢,schema migration 有版本管理,適合需要 ACID 保證的操作。

Redis(快取 + 任務佇列 + Session) 做三件事:一是 API 回應快取和 session 儲存,降低資料庫查詢壓力;二是 BullMQ 的底層 broker,處理非同步任務(通知發送、排程檢查到期實踐並自動標記完成);三是 OAuth state store,用於登入流程的防 CSRF 驗證。

這個組合的好處是概念清楚——PostgreSQL 負責所有持久化資料,Redis 負責所有暫態和非同步工作。不需要煩惱「這筆資料到底放哪個資料庫」的問題。

社交系統與通知系統

島島在 2025 年加入了完整的社交與通知功能,這是平台從「工具」轉型為「社群」的關鍵一步。

社交系統

社交功能涵蓋追蹤(follow)、雙向連結(connection)、練習夥伴申請(buddy request)、Reaction(表情回應)和留言 mention。這些功能的資料模型都在 PostgreSQL 中,透過 Prisma 管理 schema migration。

設計上有幾個值得說明的決策:

  • Reaction 採用 upsert 模式:每個使用者對同一個目標只能有一個 Reaction,重複操作是更新而非新增,避免重複資料
  • 留言 mention:前端送出 mentionedUserIds,後端在建立留言時同步觸發 P1(即時)通知,被 mention 的人會立即收到提醒
  • 隱私控制:社交功能內建隱私機制,使用者可以控制自己的實踐紀錄和學習內容的可見範圍

通知系統

通知系統是社交功能的基礎設施,基於 BullMQ + Redis 建構:

使用者操作(按讚、留言、追蹤、mention)


  Notification Service(判斷通知類型與優先級)

        ├── In-App Worker ──▶ P1 個別通知 / P2 彙整通知
        ├── Email Worker(每 4 小時批次)──▶ 合併 P1 + P2 發送
        └── Weekly Worker(每週排程)──▶ 週報摘要 Email

通知分為兩個優先級:

優先級觸發情境In-App 處理Email 處理
P1被 mention、收到夥伴申請、夥伴打卡活動即時建立個別通知每 4 小時批次發送,不彙整
P2追蹤、按讚、留言、實踐進度即時建立彙整通知每 4 小時批次發送,同類合併

Email 採用批次發送而非即時發送,避免高頻互動造成信箱轟炸。另有獨立的週報排程 worker,每週發送包含當週完成的實踐項目、收到的互動統計,以及個人化 CTA 連結的摘要信。Email 模板使用 HTML 模板引擎,支援多版本(例如歡迎信有不同的 referral group 版本)。

這套社交 + 通知架構的設計原則是:即時性和資源消耗取平衡。不是每個互動都需要即時通知,P1/P2 分級讓重要通知不被淹沒,也避免頻繁發信造成使用者疲勞。

AI 後端

AI 服務獨立成 Python FastAPI 應用(daodao-ai-backend),與 Node.js 後端分開部署。這個決策很合理:Python 在 ML 生態的工具鏈遠優於 Node.js,獨立服務也讓 AI 功能可以單獨 scale。

架構:

  • LLM 整合:推薦引擎,根據使用者的學習歷程和目標,推薦相關學習資源或社群成員
  • Qdrant:向量資料庫,儲存學習內容的 embedding,支援語意搜尋——不是關鍵字搜尋「TypeScript 教學」,而是找到「跟你的學習目標語意相近的內容」
  • ClickHouse:分析資料庫,記錄使用行為事件(頁面瀏覽、互動、學習進度),供推薦引擎的特徵工程使用
  • Redis:LLM 回應和搜尋結果快取,避免重複推論相同的 query
  • Celery:Redis-based 任務佇列,處理 AI 回饋生成等耗時的非同步任務

Node.js 後端透過 HTTP 呼叫 FastAPI 服務,兩邊各自維護自己的資料來源。

CI/CD 亮點

部署用 Docker + PM2 + GitHub Actions,有一個值得特別提的設計:TypeScript 類型感知的 Docker 層快取策略

一般的 Dockerfile 把 npm installtsc build 放在不同 layer,只有 package.json 改變才會重跑 install。但島島的 CI 還額外監控 TypeScript 型別變更:當 packages/sharedpackages/api 的型別定義有異動時,自動觸發相關 app 的 Docker layer 重建,確保型別變更不會被舊的 build cache 遮蓋。

部署完成後透過 Webhook 送 Discord 通知,包含哪個服務部署了什麼版本、build 時間、測試結果。對小團隊來說,Discord 通知比 Slack 設置成本低,也足夠用。

整體架構

瀏覽器 / Mobile App

        ├── website (Next.js :3000)
        └── product (Next.js :3001)


        daodao-server (Node.js / Express)
         │                    │
         ▼                    ▼
    PostgreSQL              Redis
    (Prisma)           │    │    │
    ├─ 使用者       BullMQ Cache Session
    ├─ 社交關係         │
    ├─ 實踐記錄         ├── 通知 Worker(P1/P2 + Email 批次 + 週報)
    └─ 通知記錄         └── 排程任務(實踐自動完成)

        daodao-server ──HTTP──▶ daodao-ai-backend
                                (Python FastAPI + Celery)
                                 │       │      │
                                 ▼       ▼      ▼
                              Qdrant  ClickHouse Redis
                           (語意搜尋)(分析)  (快取 + Celery broker)

GitHub Actions 管 CI/CD,Discord 收通知。前端 monorepo 透過 Turborepo pipeline 管 build 相依順序。

整體來說

島島這套架構的核心取捨是:用較高的技術複雜度換取每一層的最佳化空間。兩個後端服務(Node.js + Python)、多個資料庫(PostgreSQL、Redis、Qdrant、ClickHouse)、三個前端 app——對小團隊來說,這是有代價的選擇。

合理的前提是:

  1. 團隊對這些技術都有足夠熟悉度,維護成本可控
  2. 各資料庫的職責分界清楚,不會出現「這筆資料到底放哪」的混亂
  3. AI 功能是核心差異點,值得獨立投資

如果是從零開始的早期 MVP,這套架構可能過重——PostgreSQL 單一資料庫加上簡單的 Node.js API 通常可以撐到相當規模。但對一個已經明確需要語意搜尋、行為分析和多平台支援的學習平台,這個架構選擇是合理的。

Turborepo + Biome 的開發體驗確實好——lint 快、型別共用方便、多 app 的 build pipeline 清楚。這個部分是值得借鑑的,無論後端架構怎麼選。

參考資料