---
title: "回顧打造德國電子商務網站，我們做了些特別架構的設計"
description: "回顧打造德國電商網站的完整架構設計，涵蓋 Next.js App Router、Strapi CMS、Proxy API、JWT、模組化金流、SFTP、SEO 優化與 GDPR 合規等關鍵技術決策。"
canonical_url: "https://blog.markkulab.net/post/germany-ecommerce-website-architecture-design"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/author/mark-ku"
site: "Mark Ku's Blog"
date_published: "2024-10-16 20:01:35 +0800"
category: "Architecture"
tags: ["architecture", "ecommerce", "nextjs", "dotnet", "strapi", "jwt", "seo", "devops", "germany"]
language: "zh-TW"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "轉載或引用請註明作者並附上原文連結"
---

# 回顧打造德國電子商務網站，我們做了些特別架構的設計

## 系統目標 
此系統改版主要目標是對德國網站進行優化及重寫，透過這次的改版，不僅改進了舊有的系統架構上無法克服的問題及程式碼不易維護擴充的問題，希望進而提升德國網站的銷售業績，未來也可以拿新寫的網站，快速去其他國家擴展。

### 系統當時的狀況
* 系統老舊、程式碼遺失，沒有人可以在本地執行。
* 使用者不易維護原有的資料
* 舊的 API 系統架構，缺乏彈性無法擴充，無法容器化，目前仍要手動佈署。
* 舊的系統架構及擴充，也不利於 SEO 的優化。
* 舊的網站無法銷售套裝電腦及配件，目前全塞在電腦選配頁面裡銷售。
* 舊的網站三不五時因交易失敗，需要手動將暫存訂單轉成訂單。
* 舊的網站，沒有模塊化，技術框架老舊無法容器化，無法輕鬆移到其他國家使用。

## 挑戰與困難
原本招募我進來的目的是規劃及重新打造德國網站，但因為美國網站[一直有問題](https://blog.markkulab.net/nextjs-performance-issues-in-e-commerce/)及有更急的專案在經歷了充滿挑戰的第一年半，美國網站的趨近於穩定。

想說終於可以開始做德國網站，內部有著不同的大目標，礙於我們就只有三個開發人員，好幾個專案，全部一起執行會變慢，專案切來切去大家也很痛苦，來來回回去溝通了幾次，最終可以同一時間專注在一個項目上開發。

## 德國電商網站 - 架構圖
![架構設計](https://blog.markkulab.net/content/markku/posts/germany-ecommerce-website-architecture-design/images/architecture.png)
P.S. 礙於我們各種資源的限制，前端在外國網站買了 Theme，後端找了一個開源 NetCore 的專案來改。

這次採用的技術是 NextJS App routing + Dotnet core 8 ，我主要負責分析釐清需求、規劃架構、拆解工作清單，同時也參與開發核心功能開發，並獨立建置整套佈署開發環境及正式佈署開發環境。

## 接著，回顧德國電商的技術細節，做了那些特別的設計
### 前端網站( NextJS ) 

### 選擇 App routing 還是 page routing ?
當時的時空背景下，NextJS 的版更很快，每個月變動也不小，且App routing 剛stable，因此還想了好一陣子，在考量Server sdie component 的好處，可以讓不需要互動的元件的JS 更小，我試著將購買的專案模板，[從Page routing 昇級到 App routing](https://blog.markkulab.net/nextjs-upgrade-app-route/)，發現新做的專案Side effect , 應該沒這麼大，且如果採Page routing 做完之後要昇級，可能更痛。

### 為了加速開發，導入了Headless CMS - [Strapi](https://blog.markkulab.net/headless-cms-strapi/)
在過去，我們將網站設定、SEO、多語系等參數儲存在後台，透過 JSON 資料進行操作。這種維護方式確實讓我們可以快速開發，但對使用者來說卻不夠友善，沒有學過程式的人，會對這些JSON設定的作用感到困惑，上傳圖片及設定還要分開來設定，網站也常因路徑設定錯誤，出現叉燒包。  

原本是想採用自己打造的 [Low code cms](https://blog.markkulab.net/build-your-own-low-code-cms/)，來做德國網站，但礙於不夠成熟及時間不夠，最後採用了Strapi CMS 解決資料維護的問題。

### 多語系
一開始多語系原本是放在Strapi ，發現在開發階段時發現，覺得使用者在Strapi新增多語系很繁瑣，後來全部將其維護功能，移到 Excel 365，再用程式寫入Redis，使用者可以更輕鬆快速一次維護多個語系。

### Proxy api (/api/proxy/[...path].ts)
在過去使用 Next.js 時，每當需要呼叫 C# API，我們都得在 NextJS 裡額外實作一個 API，現在透過使用 http-proxy-middleware 套件來客製的代理伺服器 ，我們不再需要費時維護 NextJS 額外的 API，大幅節省了開發時間及跨域存取(CORS)的問題。
```
import { CookieKeys } from '@/const/keys';
import { clientSideLog } from '@utils/log/client-side-log';
import { LogLevel } from '@utils/log/const-logging';
import { createProxyMiddleware } from 'http-proxy-middleware';

export const config = {
    api: {
        externalResolver: true, // true, means not use the default settings of Next.js
        bodyParser: false, // false, means not use Next.js bodyParser(same as express)
    },
};

const proxy: any = createProxyMiddleware({
    target: process.env.NEXT_PUBLIC_API,
    changeOrigin: true, // change `request domain` to `target`
    pathRewrite: { '^/api/proxy': '' }, // remove `/api/proxy` prefix
    onProxyReq: relayRequestHeaders,
    onError: (err: any, req: any) => {
        clientSideLog(
            `[Proxy] Error for ${req.method} '${req.url}' to '${process.env.NEXT_PUBLIC_API}': ${err.message}`,
            LogLevel.Error
        );
    },
});

function proxyServer(req: any, res: any) {
    proxy(req, res, (err: any) => {
        if (err) {
            throw err;
        }

        throw new Error(`Request '${req.url}' is not proxied! We should never reach here!`);
    });
}

export default proxyServer;

function relayRequestHeaders(proxyReq: any, req: any, res: any) {
    let cookie = req.headers.cookie;

    if (cookie) {
        proxyReq.setHeader('cookie', cookie);
    }
}
```

> ⚠️ **注意**：上述寫法僅適用於 **Pages Router**。App Router 採用 Web Fetch API（`Request`/`Response`），不是 Node.js 的 `IncomingMessage`/`ServerResponse`，`http-proxy-middleware` 內部直接操作 Node.js 底層物件，所以無法插入。改用 App Router 的話，直接封裝 `fetch` 就夠了，不需要任何套件。

### 解決過去NEXT JS Redis Connect pool 釋放不掉的問題
頁面的渲染我們用到了大量的Redis ，原本用 node-redis，因為 Connect pool 釋放不掉，Redis 三不五時就需要手動重啟Redis，請同事用Jmeter 做了次壓力測試後，後來換成 IO redis 套件，就再也沒這個問題了。

### 後端 (Dotnet core)

### 制訂統一的 api 接口 及錯誤代碼
制定一致的API 接口，前端可以一致性的處理
```
{
  "count": 0,
  "data": null,  
  "isSuccess": true,
  "code": 20000,  
  "errors": []
}
```

### C# 非同步延申的問題
為了讓 CPU 和 IO 中達到最高的利用率，讓需要等待的執行緖，先去做其他事情，因此我們此後端API，全部採非同步的方式撰寫。  

然而，後端在非同步處理，導致在查詢日誌檔的線程時可能出現混亂，變得不好追蹤。  

為了解決這個問題，我們使用了 CorrelationId 這個套件，每次請求都會產生一個CorrelationId，並顯示在Response Headers。通過這個 [CorrelationId 套件](https://github.com/stevejgordon/CorrelationId)，並在nlog 設定後，就有利於讓我們將相關的Log 串聯起來，以便於追蹤生產環境發生的緊急問題。
![HTTP回應標頭顯示CorrelationId的截圖](https://blog.markkulab.net/content/markku/posts/germany-ecommerce-website-architecture-design/images/correlationId.png)

### Token base authentication - JWT Token
過去採用的Session，其實就不易做到橫向擴展，為了能讓後端API，未來伺服器能橫向擴展，降低api 偶合度，我們這次導入了，Token base（JWT）為辨別用戶身份的方式。  

### 模組化串接金流
自動讀取 Config 注入相關的金流Service , 並透過界面將定義所有金流需要的界口，並透過 Autofac 將金流解析，達到依賴反轉及容易擴充並解耦合，傳入payment code 就能自動抽換不同的金流邏輯。
```
   var paymentConfigs = AppSettingsHelper.GetSection<List<PaymentConfig>>("Payment", "PaymentOptions").Where(x => !x.Disable).ToList();

   foreach (var item in paymentConfigs)
   {
      var serviceTypeName = item.PaymentCode;
      Type serviceType = assembly.GetType($"MemberSite.Services.{serviceTypeName}Service");

      if (serviceType == null)
      {
         throw new Exception($"Payment {serviceType} service not found");
      }

      builder.RegisterType(serviceType)
         .WithProperty("PaymentConfig", item)
         .WithParameter(new ResolvedParameter(
         (pi, ctx) => pi.ParameterType == typeof(IUnitOfWork),
         (pi, ctx) => ctx.Resolve<IUnitOfWork>()))
         .PropertiesAutowired() // 啟用屬性自動注入               
         .Named<IPaymentOptionService>(item.PaymentCode);
   }
```
傳入payment code 就能自動依據傳入命名抽換不同的金流邏輯
```
var params = new Dictionary<string,string>();
var paymentService = _context.ResolveNamed<IPaymentOptionService>(checkout.PaymentTypeCode);
paymentService.pay(params);
```
### 暫存訂單 ( 用到抽像類別 abstract class 及物件繼承關係，讓程式可以覆用，只要維護一份程式碼 )
因為外國的金流，有些導來導去，很機會會發生交易失敗，過去的暫存訂單，會在結帳前暫存，直到真正結帳，才會寫入真的訂單，這就會有兩段類似的邏輯，且因為暫存訂單和訂單的關係，欄位相同。

繼承關係
```
[SugarTable("TempOrders")]
public class TempOrders: Orders
{   
}
```
抽像類別 
```
AbstractInsertOrderService
{
... 
public T CreateOrderInstance<T>(Checkout checkout) where T : Orders, new()
{
      return new T()
      {
		 Name = checkout.Name,
         Email = checkout.Email,
		 ...
      };
}

public int InsertOrders<T>(Checkout checkout) where T : Orders, new() // 泛型約束 (Generic Constraint)
{
  var order = CreateCustomerInstance<T>(checkout);
  return _unitOfWork.GetDbClient().Insertable<T>(order).ExecuteReturnIdentity();
}
...
} 
```
我們都知道，抽像類別是用來讓一系列類別有共用的實作，把暫存訂單和訂單共同的實作抽出來，透過抽像類別把原本兩份程式，透過抽像類別及繼承關係，變成只需要一份程式，讓程式碼開發維護起來更輕鬆。

### [Https Secure Distribution with ngnix](https://blog.markkulab.net/nginx-https-forwarding/)  
當我剛加入公司時，伺服器的架構是基於巢狀的 Hyper-V。然而，由於每台伺服器僅能使用一個 HTTPS (443) 埠，導致每次擴展 Web 應用服務時都需要啟動一個新的虛擬機器，這不僅效率低下，還浪費大量伺服器資源（如 CPU、記憶體、硬碟和外部 IP）。如今，只需要一行 Docker 指令，就能輕鬆部署多個服務，無需像過去那樣安裝作業系統和繁瑣的環境配置。由於短期內無法移轉到 [K8S](https://blog.markkulab.net/jenkins-deploy-kubernetes-with-docker-for-windows/) ，我們決定先通過 Nginx 簡化伺服器架構，提高部署效率。

### 程式產生器
為了加速開發，後端底層是採用開源的框架，自己整理過，並透過 [程式產生器](https://www.donet5.com/Doc/33) ，建立模型及資料存取層，加速後端的開發。

### 解決SFTP不穩定的問題
舊有的FTP Server 連線進去常常會斷線，試了幾套 Docker FTP後，終於架了一套擁有 Web UI 管理界面，且開源 [SFTP Server - sftpgo。](https://blog.markkulab.net/docker-web-ui-ftp-server-sftpgo/)

### Google CAPTCHA 換成 Cloudflare Turnstile  
在防止機器人攻擊方面，在登入或是查詢訂單頁面，我們將原本的Google CAPTCHA 替換了 [Cloudflare Turnstile](https://blog.markkulab.net/protect-your-website-with-cloudflare-turnstile/)，一來可以省錢(免費)，串接也更容易，且對使用者體驗更好，且比Google CAPTCHA V3又更少誤判。  
![Cloudflare Turnstile](https://blog.markkulab.net/content/markku/posts/germany-ecommerce-website-architecture-design/images/cloudflare-turnstile.png)

### SEO 的部份
歐洲對個資使用非常重視，因此不能隨意拿會員資料做行銷，因此SEO 就格外重要。
* 提交網站索引 Sitemap 到  Google search console 及 bing webmaster tools。
* 提供 Metadata Tag，更有利益爬蟲建立索引。
* 提供[結構化的資料( Google schema) 給爬蟲](https://blog.markkulab.net/search-engine-result-page/)，有助於爬蟲更理解我們的網站，並有機會獲得額外的版面。
* 修正Google search console 或是 [Bring Webmaster Tool](https://blog.markkulab.net/start-bing-seo/) 的建議及錯誤, 調整Html，讓其友善於爬蟲 ( title  50-60 個字元、Descriptions 150-160 個字元、alt 、h1 只能有一個 ... )
* 產品頁，使用[Azure AI 生成 SEO Meta Tag](https://blog.markkulab.net/generate-seo-metadata-by-azure-ai/) 資料。
* 在頁面變更時，使用了 Bing的 [Index API Now](https://www.bing.com/indexnow)，可以讓爬蟲更快到我們的網站
* 舊站的頁面永久轉址導轉(http status 308)，並將不需要的存在的頁面提交給搜尋引擎移除。


網站排名，剛上線時 4350 到 3745 名
![Web Rank](https://blog.markkulab.net/content/markku/posts/germany-ecommerce-website-architecture-design/images/webrank.png)
### 單元測試 
避免未來改功能時改壞，此次針對結帳金額或算計算運費，前後後端都有寫測試，並依據3A原則撰寫測試。  
* Arrange: 初始化目標物件、相依物件、方法參數。
* Act: 呼叫目標物件的方法。
* Assert: 驗證是否符合預期，
 
### 功能面
### 客服軟體
因為德國網站規模不大，過去沒有客服軟體，看了歐洲的通訊軟體市佔率後，我建議德國公司用了What's app 當客服軟體，增加和使用者互動的機會。

### 導入Stripe 金流
Stripe 的金流串接相當容易，只要串好一個就能透過後台開啟10幾種歐洲的金流，因此在上線後兩週就能很快速的擴充新金流，且可以啟用(3DS)簡訊驗證，讓交易更安全。

### 另一個要注意的是GDPR
根據 GDPR 的規範，凡是有歐盟境內人士造訪的網站，都得通過 Cookie 彈窗明確告知使用 GA 並徵得用戶同意，這個同意必須是具體的、自願的、明確的，且可撤回的。

### 避免詐欺保險
歐洲及美國，網路詐欺嚴重，過去也因為刷退容易太容易，將 Paypal 關閉，但因為美國擁有可以透過詐欺保險服務來避免，但不服務於歐洲市場，因此我透過電商朋友的朋友問到，Riskfield，但因每月基本費用有點高，每筆還要額外抽%，因此 先不考慮。  
![Riskfield](https://blog.markkulab.net/content/markku/posts/germany-ecommerce-website-architecture-design/images/riskfield.png)

## 上線前 DevOps 遇到的一些比較特別問題
[因為遇到的問題比較多，因此額外寫了一篇Blog](https://blog.markkulab.net/germany-website-go-live-encountered-special-technical-problems/)

## 上線後兩個月，公司要往其他國家拓展

當初前端寫的多語系，在拓展到些歐洲國家時，因為一個國家的官方語言就有5種以上，非官方語言就有1X種，當時並未考量到大量語言的擴展問題，因此，我們將語系設定全部抽到前端的 const 去做集中管理管理，並將一些功能不要挷定在特定語系，減少對於單一語系的判斷或設定，儘可能通用，或是將不通用的抽成設定。

## 結論
德國專案其實並不是從2月中才開始的，因為本身就對架構規劃有興趣，早在我剛加入公司時，只要有空閒，我就開始著手研究架構及規劃，從舊有的程式碼中推導出需求，並思考如何縮短開發時間，儘管開發過程中遇到不少小插曲，但最終我們在7月1號成功上線，德國網站如期順利運行，歐洲市場也逐步進入正軌。  

這次能夠順利上線，過程中也獲得了許多人的幫助，也非常感謝大家，由於一直沒有專職PM，設計主管和我一直兼任這個角色，加上資源有限，我們在一邊規劃、一邊開發的同時，還需要協調外部事務，最終能夠達成這個目標，真的很不容易。

---

## 關於本文與作者

本文出自 [Mark Ku's Blog](https://blog.markkulab.net/post/germany-ecommerce-website-architecture-design)

授權條款： [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — 轉載或引用請註明作者並附上原文連結

### 關於作者

**[Mark Ku](https://blog.markkulab.net/author/mark-ku)** — Software Solution Provider

- 10+ 年資深軟體工程師，現為 AI 應用 Builder
- 專注大型平台架構設計，從北美電商到AI SaaS訂閱收費系統
- 結合 AI Agent 與自動化，打造高效可演進的產品技術基礎

### 作者開發的免費工具

以下工具皆可免費使用：

- [免費 PDF 簽名工具](https://blog.markkulab.net/tools/pdf-sign): 線上 PDF 簽名工具，瀏覽器內完成手繪、打字、上傳簽名，可拖曳放置、縮放、下載。所有處理都在你的裝置完成，檔案不會上傳。
- [VS Code Refactory](https://blog.markkulab.net/tools/refactory): Refactory 是一款 VS Code 重構擴充套件：34 個重構動作、37 條 code smell 檢查、Code Health 儀表板、18 種語言、534 支測試。懂你的專案慣例：介面放哪、DI 註冊寫在哪、'use client' 該不該加；還會用 git 修改頻率 × 複雜度排出「該先修哪個檔案」，並一鍵把壞味道交給你自己電腦上的 Claude Code 修。免費使用，原始碼不離開你的機器。
- [DB-Kit 資料庫管理工具](https://blog.markkulab.net/tools/db-kit): DB-Kit 是一個用 Tauri + Rust + React 打造的輕量跨平台資料庫管理工具，用單一一致的介面同時管理 MySQL、MariaDB、PostgreSQL、SQL Server、Oracle、SQLite、MongoDB、Redis、Kafka、Elasticsearch 與 RabbitMQ 十一種資料來源：連線密碼以 OS keychain 加密、SSH Tunnel、完整 CRUD、視覺化查詢建構器、多結果集同時顯示、跨連線資料傳輸與比對同步、Excel / CSV 匯入匯出、執行計畫視覺化、ER 圖、排程備份、SQL 壓力測試（p50～p99 延遲百分位）、15 條規則的 SQL 審查、Kafka 訊息瀏覽與監控告警；繁中 / 英文雙語介面，內建 AI 助手（自然語言生成 SQL、AI 審查與調校建議）與命令列工具 dbk。免費開源（MIT），提供 Windows / macOS / Linux 安裝檔。
- [VS Code Super Mermaid](https://blog.markkulab.net/tools/super-mermaid): Super Mermaid 是一款 VS Code 擴充套件：開箱即用的漂亮 Mermaid 圖表，自動上色、即時預覽、滑鼠平移縮放、PNG / SVG 高解析匯出，內建 21 種範本與多種主題。免費開源（MIT）。
- [React Super Mermaid](https://blog.markkulab.net/tools/react-super-mermaid): react-super-mermaid 是一個開源 React 元件庫：一行 <MermaidViewer> 即可渲染漂亮的 Mermaid 圖表，內建 colorful / sketch 主題、平移縮放、圖內搜尋、SVG / PNG 高解析匯出。輕量、SSR 安全、完整 TypeScript 型別。免費開源（MIT）。
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/tools/jira-super-mermaid): Atlassian Forge app：在 Jira issue 與 Confluence 內文直接寫 Mermaid 語法，畫流程圖、時序圖、狀態機與甘特圖。11 種圖表、SVG / PNG 匯出、明暗主題、完整中日韓文字支援。取得 Runs on Atlassian 資格：圖表存在你自己的站台，app 不呼叫任何第三方服務。免費，即將上架 Atlassian Marketplace。
- [Mermaid 線上預覽](https://blog.markkulab.net/tools/mermaid-preview): 在瀏覽器裡寫 Mermaid、即時看圖，整張圖表壓進網址就能分享。免註冊、不上傳伺服器，相容 mermaid.live 的分享連結。
- [React Intl Phone Number](https://blog.markkulab.net/tools/react-intl-phone-number): react-intl-phone-number 是一個開源 React 元件：framework-agnostic、不依賴 antd，提供 E.164 進出、可搜尋國旗 / 國碼下拉、可配置驗證等級（strict / mobile-strict / loose）、可主題化 CSS 與 i18n，電話邏輯由 google-libphonenumber 驅動。輕量、完整 TypeScript 型別。免費開源（MIT）。
- [Uptime Kuma Cluster](https://blog.markkulab.net/tools/uptime-kuma-cluster): 把單機版 Uptime Kuma 改造成高可用叢集：OpenResty + Lua 智慧負載平衡、MariaDB 共享狀態、健康檢查與自動 Failover，附叢集管理 REST API，一行 Docker Compose 啟動。免費開源（MIT）。
- [特教專案](https://blog.markkulab.net/education): 為特殊教育學生製作的學習教材

### 每日 Podcast

- [科技新鮮事](https://blog.markkulab.net/category/tech-news): 每日精選 AI 與科技趨勢，透過語音摘要快速掌握最新技術動態，涵蓋 AI 應用、軟體架構、DevOps 與工程實戰。 — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/category/ai-stock-chat): 每個交易日用 AI 分析台股盤勢，以雙人對話聊當天的盤中觀察與隔日預測。 — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml

### 電子報

[訂閱電子報](https://blog.markkulab.net/subscribe) — 第一時間收到新文章通知，無垃圾信、隨時可取消訂閱。
