跳至主要内容

7 篇文章 含有標籤「軟體工程」

檢視所有標籤

Pinia Setup Store 與 Option Store 比較介紹入門教學筆記

· 閱讀時間約 4 分鐘
kdchang

前言

Pinia 是 Vue 3 推薦的狀態管理工具,作為 Vuex 的繼任者,Pinia 以簡潔且直觀的 API 設計,為 Vue 生態系帶來更輕量且易於維護的解決方案。Pinia 提供兩種撰寫 store 的方式:Option Store 與 Setup Store。兩者皆支援響應式狀態管理,但在語法結構與彈性上有所不同。理解這兩種寫法的差異,對於新手以及想提升開發效率的工程師來說相當重要。


重點摘要

  • Option Store 是物件式語法,結構清晰,適合剛接觸 Pinia 或 Vuex 過渡的開發者。
  • Setup Store 採用 Composition API 語法,彈性更大,易於搭配 Vue 3 的其它 API 使用。
  • Option Store 通常使用 stategettersactions 三大屬性分隔管理狀態、計算屬性與方法。
  • Setup Store 透過 refreactive 直接定義響應式狀態,並以函式回傳公開 API,結構更自由。
  • Setup Store 更方便與 computedwatchasync/await 等 Composition API 功能整合。
  • Option Store 語法更接近 Vuex,學習門檻較低,但在複雜邏輯時不易拆分。
  • Setup Store 支援多 store 之間更靈活的組合與重用。

1. Option Store 範例

// stores/counterOption.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount(state) {
return state.count * 2;
},
},
actions: {
increment() {
this.count++;
},
incrementBy(amount) {
this.count += amount;
},
},
});

特點說明

  • state 是一個函式,回傳物件,定義響應式資料。
  • getters 為基於 state 的計算屬性,類似 Vue 的 computed。
  • actions 用於定義操作狀態的方法,且可包含異步邏輯。
  • 語法簡單,概念清晰,適合快速上手。

2. Setup Store 範例

// stores/counterSetup.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useCounterStore = defineStore('counter', () => {
const count = ref(0);

const doubleCount = computed(() => count.value * 2);

function increment() {
count.value++;
}

function incrementBy(amount) {
count.value += amount;
}

return {
count,
doubleCount,
increment,
incrementBy,
};
});

特點說明

  • 直接使用 Vue Composition API 的 refcomputed
  • 可自由使用所有 Vue Composition API 來封裝邏輯。
  • 方法與狀態同時定義在函式作用域內,結構更模組化。
  • 支援更複雜狀態邏輯與副作用控制。

3. 比較分析

項目Option StoreSetup Store
語法風格物件式,類似 Vuex函式式,基於 Composition API
學習曲線容易入門,結構清楚需要熟悉 Vue 3 Composition API
狀態管理透過 state 函式返回物件使用 refreactive 定義狀態
計算屬性透過 getters 定義使用 computed 定義
方法定義放在 actions直接函式內定義,結構更自由
邏輯拆分較不靈活,所有邏輯集中可拆分成多個函式模組,彈性更高
異步處理支援,寫法較直觀支援,且更方便搭配 async/await
重用性較弱易於組合與重用,搭配 Composition API 更佳
持久化插件支持兩者皆支持兩者皆支持
與 Vue 3 整合一般深度整合,充分發揮 Vue 3 特性

4. 什麼時候用哪種?

  • **初學者或 Vuex 使用者:**建議先使用 Option Store,語法熟悉、過渡平滑。
  • **專案中需大量組合邏輯、重用邏輯時:**Setup Store 更適合,也更符合 Vue 3 生態。
  • **大型專案或團隊合作:**Setup Store 可拆分細節更清楚,便於維護與測試。
  • **希望在 Store 中使用複雜副作用(例如 watch、watchEffect):**必須使用 Setup Store。

5. 總結與建議

Pinia 的 Option Store 與 Setup Store 各有優劣,根據團隊熟悉度、專案規模與需求選擇合適的寫法。掌握兩者能幫助你靈活應對不同開發場景。


補充:如何在專案中使用

  1. 安裝 Pinia:
npm install pinia
  1. main.js 初始化:
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount('#app');
  1. 直接在組件中使用 store:
<script setup>
import { useCounterStore } from '@/stores/counterSetup';

const counter = useCounterStore();

function increase() {
counter.increment();
}
</script>

<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="increase">增加</button>
</div>
</template>

Django Middleware 入門教學筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在 Web 應用程式中,每一次的 HTTP 請求與回應都會經過一連串的處理流程。在 Django 框架中,這些處理流程的攔截點與操作邏輯就是透過「Middleware(中介軟體)」來實現。

Middleware 是 Django 請求與回應處理流程中一個極為關鍵的機制,允許開發者在請求進入 View 前或回應送出前進行特定操作,例如:身份驗證、日誌紀錄、權限控制、跨域處理、錯誤攔截、壓縮回應等。

掌握 Middleware 的基本原理與撰寫方式,將有助於你更靈活地控制整個應用程式的行為與安全性。


重點摘要

  • Middleware 是介於「請求 → View → 回應」流程中,可插入自訂邏輯的中介處理元件。

  • 每個 Middleware 類別皆需實作特定的方法,如 __call__()process_request()process_response()

  • Middleware 可用於:日誌、認證、CORS、壓縮、IP 限制、防止 CSRF 等。

  • 所有 Middleware 的執行順序依 settings.pyMIDDLEWARE 設定順序為準。

  • Middleware 執行順序為:

    • 請求階段(request):從上往下
    • 回應階段(response):從下往上

Middleware 執行流程簡述

以下是 Django 處理請求的標準流程:

Request

Middleware(request)→ View 處理 → Middleware(response)

Response

舉例來說,假設你有三個 Middleware:

MIDDLEWARE = [
'myproject.middleware.FirstMiddleware',
'myproject.middleware.SecondMiddleware',
'myproject.middleware.ThirdMiddleware',
]

執行順序會是:

  • 請求階段:First → Second → Third → View
  • 回應階段:Third → Second → First → Client

Middleware 的基本結構與方法

從 Django 1.10 起,Middleware 使用「新式 Middleware」架構,基於 __call__() 方法設計。基本範例如下:

# middleware.py
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 這裡是初始化,只在伺服器啟動時執行一次

def __call__(self, request):
# 處理請求邏輯(View 前)
print("Before view")

response = self.get_response(request)

# 處理回應邏輯(View 後)
print("After view")

return response

實際範例一:請求時間計算 Middleware

此範例會計算 View 執行所花費的時間,並加入到 HTTP 回應標頭中。

# middleware.py
import time

class TimerMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
response["X-Process-Time"] = f"{duration:.4f}s"
return response

settings.py 中啟用:

MIDDLEWARE = [
# 其他內建 middleware...
'myapp.middleware.TimerMiddleware',
]

當你訪問任意頁面時,HTTP 回應標頭中會出現類似:

X-Process-Time: 0.0321s

實際範例二:只允許特定 IP 的 Middleware

你可以建立一個 Middleware,用來限制只有某些 IP 位址可以訪問網站。

# middleware.py
from django.http import HttpResponseForbidden

class IPWhitelistMiddleware:
ALLOWED_IPS = ["127.0.0.1", "192.168.1.100"]

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
ip = request.META.get("REMOTE_ADDR")
if ip not in self.ALLOWED_IPS:
return HttpResponseForbidden("Access denied.")
return self.get_response(request)

這可以用來防止內部 API 被外部訪問。


常見用途與建議實作

用途建議工具或方式
請求/回應紀錄實作 logging Middleware
使用者權限控管自訂 Middleware 或使用 Django Authentication
防止過多請求與 Redis 結合實作 Rate Limiting Middleware
跨域(CORS)處理使用 django-cors-headers 套件
錯誤攔截與通知捕捉例外並結合外部通知工具(如 Sentry)

注意事項與開發建議

  1. Middleware 應保持單一職責:一個 Middleware 做一件事,方便測試與維護。
  2. 避免過度耦合 View 或 Model:Middleware 應專注於請求與回應處理,不應過度介入業務邏輯。
  3. 注意 Middleware 順序:Django 會依照 MIDDLEWARE 陣列順序執行,不當排序可能導致無效或錯誤。
  4. 善用內建 Middleware:如 SecurityMiddleware, CommonMiddleware, AuthenticationMiddleware,無須重造輪子。
  5. 除錯技巧:可在 Middleware 中使用 print()logging 或加入 HTTP 標頭協助觀察執行情況。

總結

Middleware 是 Django 框架中攔截與處理請求/回應流程的核心機制之一。學會撰寫與使用 Middleware,能讓你更有效地實現各種橫向功能(如安全、效能、紀錄等),並保持應用架構的乾淨與可維護性。

透過本教學筆記,你應該已理解:

  • Middleware 的基本概念與執行流程
  • 如何撰寫一個簡單的 Middleware 類別
  • 幾種常見的 Middleware 實用場景與範例

進一步建議可以閱讀 Django 官方文件中的 Middleware 章節,並觀察第三方套件的 Middleware 是如何實作的,將更有助於理解其威力與應用彈性。

Django 中的 n+1 問題入門教學筆記

· 閱讀時間約 4 分鐘
kdchang

前言

Django 作為一個功能完整的 Python Web 框架,其 ORM(Object-Relational Mapping)能讓開發者以物件導向方式操作資料庫。然而,這樣的便利也容易隱藏一些效能陷阱,其中最常見也最容易忽略的就是 n+1 查詢問題(n+1 query problem)

n+1 問題會導致程式在執行查詢時產生大量多餘的 SQL 語句,影響效能並拖慢頁面載入速度,特別是在處理關聯資料時(如 ForeignKey 或 ManyToManyField)。本篇筆記將帶你認識 n+1 問題在 Django 中的成因、辨識方法與解法。


重點摘要

  • n+1 問題定義:查詢一個主物件(n 筆),卻對每筆物件再執行一次額外查詢,總共造成 n+1 次查詢。

  • 常見發生情境:在模板或程式中存取 ForeignKey 或 ManyToManyField 時,未預先載入(eager loading)相關資料。

  • 效能影響:每個物件觸發一次額外 SQL,當資料量增加時,查詢數可能達到數百次以上。

  • 解法

    • 使用 select_related() 預先載入「多對一」與「一對一」的關聯。
    • 使用 prefetch_related() 預先載入「一對多」與「多對多」的關聯。
  • 如何偵測 n+1 問題

    • 開啟 django.db.backends 日誌觀察查詢數量與內容。
    • 使用 Django Debug Toolbar 查看 SQL 查詢次數與細節。

實際範例:部落格文章與作者

假設有以下兩個模型:

# models.py
class Author(models.Model):
name = models.CharField(max_length=100)

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)

n+1 問題範例

# views.py
def post_list(request):
posts = Post.objects.all()
return render(request, "blog/post_list.html", {"posts": posts})
{# post_list.html #}
<ul>
{% for post in posts %}
<li>{{ post.title }} - {{ post.author.name }}</li>
{% endfor %}
</ul>

這段程式會發生 n+1 問題:

  • 第一次查詢取得所有文章 SELECT * FROM post
  • 每篇文章查詢一次作者 SELECT * FROM author WHERE id = ?

如果有 100 篇文章,總共會執行 101 次查詢。


# views.py(改進)
def post_list(request):
posts = Post.objects.select_related("author").all()
return render(request, "blog/post_list.html", {"posts": posts})

select_related 會使用 SQL JOIN 一次把作者資料一起載入。查詢數減少為 1 次:

SELECT post.*, author.*
FROM post
JOIN author ON post.author_id = author.id;

這種方式適合用於 ForeignKey(多對一)與 OneToOneField 關聯。


若改為一對多或多對多關係,例如:

class Tag(models.Model):
name = models.CharField(max_length=30)

class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag)

如果在模板中使用:

{% for post in posts %}
{{ post.title }}:
{% for tag in post.tags.all %}
{{ tag.name }}
{% endfor %}
{% endfor %}

這樣會產生 n+1 查詢問題(每個 post 查一次 tag)。解法:

posts = Post.objects.prefetch_related("tags").all()

prefetch_related() 會先查出所有關聯,再用 Python 記憶體關聯對應資料,不用 JOIN。

SELECT * FROM post;
SELECT * FROM post_tags WHERE post_id IN (...);
SELECT * FROM tag WHERE id IN (...);

特性select_relatedprefetch_related
關係類型一對一、外鍵(ForeignKey)一對多、多對多
查詢方式使用 JOIN 一次查出分別查詢後用 Python 關聯
查詢數量一次查詢即可完成最少兩次查詢
效能適用情境關聯資料不多且關係單純關聯資料多或複雜嵌套

如何偵測與除錯 n+1 問題

  1. 開啟 SQL 日誌: 在 settings.py 中設定:

    LOGGING = {
    "version": 1,
    "handlers": {
    "console": {
    "class": "logging.StreamHandler",
    },
    },
    "loggers": {
    "django.db.backends": {
    "handlers": ["console"],
    "level": "DEBUG",
    },
    },
    }
  2. 使用 Django Debug Toolbar: 安裝與設定後可視覺化查詢次數與內容。

  3. 查看 QuerySet 查詢次數: 使用 len(connection.queries) 或中間件分析每個 request 的查詢數。


總結

n+1 問題是 Django ORM 中最常見的效能陷阱之一,但只要了解其原理與解法,透過 select_related()prefetch_related() 搭配得當,幾乎可以完全避免這個問題。

掌握以下原則即可:

  • 遇到 ForeignKey 或 OneToOne 時用 select_related()
  • 遇到 ManyToMany 或反向 ForeignKey 時用 prefetch_related()
  • 避免在模板中直接使用 .related_set.all() 未預先載入資料
  • 對列表頁或頻繁查詢頁進行效能測試與 SQL 分析

良好的 ORM 使用習慣能大幅提升系統穩定性與使用者體驗,是每位 Django 開發者必備的基礎功。

GraphQL 入門教學筆記(ESM 模組版)| 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在傳統的 REST API 中,前端往往需要根據不同的需求,呼叫多個端點取得資料,有時還會遇到資料過多(Over-fetching)或不足(Under-fetching)的問題。為了解決這些痛點,Facebook 在 2015 年開源了 GraphQL,一種靈活且高效率的查詢語言。

GraphQL 讓前端可以明確指定想要的資料欄位,伺服器僅回傳需要的資料,改善資料浪費與重複請求的問題。此語言既可以查詢(Query)、也可以修改資料(Mutation),更支援訂閱(Subscription)即時變化,成為 REST 的強大替代方案。

本文將以 ESM(ECMAScript Module)模組格式,搭配現代 Node.js 環境與 Apollo Server,手把手實作一個簡易 GraphQL API。


重點摘要

  • GraphQL 是什麼? 一種用於 API 的查詢語言(Query Language),由 Facebook 開發。它不是資料庫,而是用來建立伺服器與用戶端之間通訊的一種規格。

  • 特色與優點

    • 客戶端可以精確指定所需資料
    • 單一端點處理所有請求
    • 減少 over-fetching / under-fetching 問題
    • 強型別系統(Schema 定義清楚 API 結構)
    • 自帶文件化功能(如 Apollo Studio / GraphQL Playground)
  • 核心概念

    • Schema:定義資料型別與查詢結構
    • Query:讀取資料
    • Mutation:修改資料(新增、刪除、更新)
    • Resolver:實際執行資料存取邏輯的函式
    • Type:定義資料欄位與型別,例如 type Book { title: String }
  • GraphQL vs REST

    特性RESTGraphQL
    資料請求多個端點單一端點
    回傳資料固定欄位,可能過多或不足客戶端可自訂所需欄位
    文件維護手動撰寫 Swagger/OpenAPI自動產生於 Playground / Studio
    資料關聯查詢多次 HTTP 請求一次查詢可取得巢狀關聯資料

實作範例(Node.js + Apollo Server + ESM)

一、專案初始化

建立資料夾並初始化:

mkdir graphql-esm-demo
cd graphql-esm-demo
npm init -y
npm install @apollo/server graphql

修改 package.json 啟用 ESM 模組:

{
"type": "module"
}

二、撰寫 GraphQL API(server.js)

建立 server.js

// server.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// 模擬資料
const books = [
{ title: 'GraphQL 教學指南', author: 'Alice' },
{ title: '現代 JavaScript', author: 'Bob' },
];

// 定義 Schema
const typeDefs = `
type Book {
title: String
author: String
}

type Query {
books: [Book]
}
`;

// 定義 Resolver
const resolvers = {
Query: {
books: () => books,
},
};

// 建立 Apollo Server 實例
const server = new ApolloServer({ typeDefs, resolvers });

// 啟動伺服器
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});

console.log(`🚀 Server ready at ${url}`);

三、啟動伺服器並測試

執行伺服器:

node server.js

啟動後會顯示伺服器位置(通常是 http://localhost:4000 Apollo Studio,或使用 Postman / Altair / curl 測試。


四、查詢範例(GraphQL Playground 中輸入)

query {
books {
title
author
}
}

我們也可以僅查詢某個欄位,例如:

query {
books {
title
}
}

這正是 GraphQL 的威力所在:我們只拿我們需要的資料。


延伸功能與建議學習方向

  • Mutation 實作(新增、刪除資料) 可以加入 Mutation 類型與 resolver,實作 POST/PUT 功能。

  • 資料來源整合 Resolver 可以整合 REST API、資料庫、第三方服務等來源。

  • 驗證與權限 可透過 Context 傳入使用者驗證資訊,進行資料存取控管。

  • 使用工具

    • Apollo Studio:視覺化介面與測試工具
    • GraphQL Codegen:產生型別定義與前端 Hooks
    • Relay / urql / Apollo Client:前端整合工具

總結

GraphQL 提供比 REST 更彈性與效率的 API 設計方式,尤其適合需要頻繁前後端協作或資料結構變化頻繁的專案。透過本篇教學,我們已經學會如何使用 Apollo Server 以 ESM 模組撰寫簡單的 GraphQL API,並實際查詢資料。

未來我們可以嘗試進一步加入 Mutation、串接資料庫(如 MongoDB、PostgreSQL)、前端 Apollo Client 等,打造一個全端的 GraphQL 應用。

常見軟體工程師/Web 開發技術面試及學習資源整理

· 閱讀時間約 2 分鐘
kdchang