應用程式架構
建構 Shopify 應用程式需要了解您的應用程式如何在每一層與 Shopify 平台整合——從身份驗證和工作階段管理到 UI 渲染和事件處理。本課程涵蓋基礎架構決策和您建構生產級 Shopify 應用程式所需的模式。
應用程式類型
Shopify 支援三種主要的應用程式類型,各適合不同的使用案例:
公開應用程式
公開應用程式列在 Shopify App Store 上,任何商家都可以安裝。它們需要經過應用程式審核流程,且必須符合 Shopify 的品質、安全性和效能要求。
- 透過 Shopify App Store 分發
- 與每個商家進行基於 OAuth 的身份驗證
- 必須處理多租戶資料隔離
- 與 Shopify 分享收入(Shopify 對前 100 萬美元收取 0%,之後收取一定百分比)
自訂應用程式
自訂應用程式為單一商家或組織建構。它們不會列在 App Store 上,而是直接透過 Shopify 管理後台安裝。
- 為特定商店或 Plus 組織建構
- 使用自訂應用程式存取權杖(無需 OAuth 流程)
- 因為僅服務一個租戶,架構更簡單
- 無需 App Store 審核
草稿應用程式
草稿應用程式是尚未提交審核的開發中應用程式。可以安裝在開發商店上進行測試。
- 透過合作夥伴控制台或 Shopify CLI 建立
- 透過安裝 URL 安裝在開發商店上
- 用於測試和開發的完整功能
- 準備好時轉換為公開或自訂應用程式
如果您為特定客戶建構,從自訂應用程式開始。如果您為生態系統建構產品,從草稿應用程式開始,準備好後轉換為公開應用程式。本課程中的架構模式適用於所有三種類型。
Remix 應用程式模板
Shopify 的官方應用程式骨架建構於 Remix 上,這是一個全端 React 框架。此模板開箱即用提供您需要的一切:
# 使用 Remix 模板建立新的 Shopify 應用程式
shopify app init --template remix
# 建構後的專案結構
my-shopify-app/
├── app/
│ ├── routes/
│ │ ├── app._index.tsx # 應用程式主頁面
│ │ ├── app.tsx # 帶導覽的應用程式佈局
│ │ ├── auth.$.tsx # OAuth 回呼處理
│ │ ├── auth.login/
│ │ │ └── route.tsx # 登入頁面
│ │ └── webhooks.tsx # Webhook 處理器
│ ├── shopify.server.ts # Shopify API 設定
│ └── entry.server.tsx # 伺服器進入點
├── extensions/ # 應用程式擴充功能
├── prisma/
│ └── schema.prisma # 資料庫結構描述
├── shopify.app.toml # 應用程式設定
├── package.json
└── remix.config.js
關鍵檔案說明
shopify.server.ts 檔案是您 Shopify 整合的核心。它設定身份驗證、API 存取和工作階段儲存:
// app/shopify.server.ts
import "@shopify/shopify-app-remix/adapters/node";
import {
ApiVersion,
AppDistribution,
shopifyApp,
DeliveryMethod,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY!,
apiSecretKey: process.env.SHOPIFY_API_SECRET!,
apiVersion: ApiVersion.January25,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL!,
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
PRODUCTS_UPDATE: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
},
hooks: {
afterAuth: async ({ session }) => {
// OAuth 成功後註冊 webhooks
shopify.registerWebhooks({ session });
},
},
});
export default shopify;
export const apiVersion = ApiVersion.January25;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;
App Bridge 4.x
App Bridge 是將您的應用程式前端連接到 Shopify 管理後台的 JavaScript 函式庫。4.x 版本是一次重大改版,簡化了整合模式。
App Bridge 的功能
- 導覽:您的應用程式出現在 Shopify 管理後台的 iframe 內。App Bridge 處理導覽、返回按鈕和 URL 同步。
- 身份驗證:管理工作階段權杖並確保 API 請求已正確驗證。
- UI 整合:提供存取 Shopify 管理後台 UI 元素的能力,如 toast、模態框和上下文列。
- 資源選取器:內建元件讓商家選取產品、商品系列和其他 Shopify 資源。
Remix 中的 App Bridge
使用 Remix 模板時,App Bridge 會自動設定。app.tsx 佈局元件包裹您的應用程式:
// app/routes/app.tsx
import { Outlet } from "@remix-run/react";
import { AppProvider } from "@shopify/shopify-app-remix/react";
import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
export default function App() {
return (
<AppProvider isEmbeddedApp apiKey={window.ENV.SHOPIFY_API_KEY}>
<Outlet />
</AppProvider>
);
}
App Bridge 4.x 移除了早期版本中使用的 createApp() 函式模式。如果您從 App Bridge 3.x 遷移,您不再需要建立明確的 app 實例。相反,當您的應用程式在 Shopify 管理後台中載入時,App Bridge 會自動初始化。直接的 window.shopify 呼叫取代了舊的 app.dispatch() 模式。
Polaris 設計系統
Polaris 是 Shopify 的設計系統,提供與 Shopify 管理後台外觀一致的 React 元件。使用 Polaris 確保您的應用程式對商家來說感覺是原生的。
// app/routes/app._index.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import {
Page,
Layout,
Card,
BlockStack,
Text,
DataTable,
Badge,
Button,
InlineStack,
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`
query {
orders(first: 10, sortKey: CREATED_AT, reverse: true) {
edges {
node {
id
name
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
displayFulfillmentStatus
createdAt
}
}
}
}
`);
const data = await response.json();
return json({ orders: data.data.orders.edges.map((e: any) => e.node) });
};
export default function Index() {
const { orders } = useLoaderData<typeof loader>();
const rows = orders.map((order: any) => [
order.name,
`${order.totalPriceSet.shopMoney.amount} ${order.totalPriceSet.shopMoney.currencyCode}`,
order.displayFulfillmentStatus,
new Date(order.createdAt).toLocaleDateString(),
]);
return (
<Page title="Dashboard" primaryAction={{ content: "Sync Data" }}>
<Layout>
<Layout.Section>
<Card>
<BlockStack gap="300">
<InlineStack align="space-between">
<Text as="h2" variant="headingMd">
近期訂單
</Text>
<Badge tone="success">Live</Badge>
</InlineStack>
<DataTable
columnContentTypes={["text", "numeric", "text", "text"]}
headings={["訂單", "總計", "出貨狀態", "日期"]}
rows={rows}
/>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
Remix 模板附帶 Polaris 12.x+。升級時請務必查看 Polaris 遷移指南,因為元件 API 在主要版本之間會改變。BlockStack 和 InlineStack 元件在 Polaris 12 中取代了舊的 Stack 元件。
工作階段管理
Shopify 應用程式必須為已驗證的商家管理工作階段。工作階段包含商店網域、存取權杖和權限範圍。Remix 模板透過 Prisma 支援的工作階段儲存來處理此問題。
工作階段流程
工作階段儲存設定
// prisma/schema.prisma
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
@@index([shop])
}
永遠不要記錄、在客戶端程式碼中暴露或將 Shopify 存取權杖儲存在 cookie 中。存取權杖在您的應用程式範圍內授予對商家商店的完整 API 存取。將它們加密儲存在資料庫中,並且只在伺服器端存取。
Webhooks 與事件驅動架構
Webhooks 是當商家商店中發生事件時,Shopify 傳送給您的應用程式的 HTTP 回呼。它們是反應式應用程式行為的基礎。
註冊 Webhooks
Webhooks 在 shopify.server.ts 中宣告,並在 OAuth 後自動註冊:
// 在 shopify.server.ts webhooks 設定中
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
ORDERS_CREATE: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
PRODUCTS_UPDATE: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
CUSTOMERS_DATA_REQUEST: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
CUSTOMERS_REDACT: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
SHOP_REDACT: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
},
處理 Webhooks
// app/routes/webhooks.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import db from "../db.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const { topic, shop, session, admin, payload } =
await authenticate.webhook(request);
switch (topic) {
case "APP_UNINSTALLED":
if (session) {
await db.session.deleteMany({ where: { shop } });
}
// 清除此商店的應用程式資料
await db.appData.deleteMany({ where: { shop } });
break;
case "ORDERS_CREATE":
// 處理新訂單
await processNewOrder(shop, payload);
break;
case "PRODUCTS_UPDATE":
// 同步產品變更
await syncProductUpdate(shop, payload);
break;
case "CUSTOMERS_DATA_REQUEST":
// GDPR:回傳顧客資料
await handleDataRequest(shop, payload);
break;
case "CUSTOMERS_REDACT":
// GDPR:刪除顧客資料
await handleCustomerRedact(shop, payload);
break;
case "SHOP_REDACT":
// GDPR:刪除所有商店資料
await handleShopRedact(shop, payload);
break;
default:
console.warn(`未處理的 webhook 主題:${topic}`);
}
return new Response(null, { status: 200 });
};
每個 Shopify 應用程式必須處理三個 GDPR webhooks:CUSTOMERS_DATA_REQUEST、CUSTOMERS_REDACT 和 SHOP_REDACT。如果這些未正確實作,您的應用程式將無法通過 App Store 審核。即使您的應用程式不儲存顧客資料,您也必須以 200 回應確認這些 webhooks。
Webhook 最佳實務
- 始終快速回應 200——非同步處理 webhook 有效負載。Shopify 期望在 5 秒內收到回應。
- 處理冪等性——Shopify 可能多次傳送相同的 webhook。使用
X-Shopify-Webhook-Id標頭進行去重。 - 驗證 HMAC 簽名——
authenticate.webhook()呼叫會處理此問題,但如果您建構自訂處理器,請始終驗證X-Shopify-Hmac-SHA256標頭。 - 監控傳遞失敗——Shopify 在 48 小時內重試失敗的 webhooks。如果您的端點持續失敗,Shopify 將移除註冊。
架構決策圖
現在您已了解架構基礎,請繼續閱讀 Admin UI 擴充功能 以了解如何使用自訂 UI 元件擴展 Shopify 管理後台。