Shopify webhook là khái niệm có lẽ đã khá quen thuộc với anh em Dev khi phát triển ứng dụng trên shopify. Hầu như bất cứ app nào cũng phải đăng ký các webhook của shopify để có thể đồng bộ dữ liệu trong app với dữ liệu của store. Mặc dù rất quen thuộc nhưng đôi khi chúng ta sẽ ít để ý đến việc sử dụng sao cho tối ưu và tránh những rắc rối của nó. Trong bài viết này chúng ta sẽ cùng tìm hiểu sơ bộ lại về shopify webhook và những giải pháp giảm thiểu ảnh hưởng của nó nhé.
I. Shopify webhook là gì
- Webhook là một tính năng cho phép website tự động thông báo và gửi dữ liệu thời gian thực đến các hệ thống khi có một sự kiện nào đó phát sinh trên website (ví dụ như khánh hàng đăng ký, điền form, mua hàng, hay gửi email) .
- Với Shopify, Webhook cho phép các ứng dụng giữ kết nối với dữ liệu của Shopify hoặc thực hiện một hành động sau khi một sự kiện cụ thể xảy ra trong cửa hàng.
- Webhook là một giải pháp hiệu quả thay cho việc liên tục kiểm tra các thay đổi trong dữ liệu của cửa hàng. Cùng xem sơ đồ bên dưới để hiểu rõ hơn:
II. Hướng dẫn sử dụng Shopify Webhooks
Việc sử dụng shopify webhook khá đơn giản, đầu tiên chúng ta cần đăng ký webhook topic khi store cài app, sau đó nhận và xử lý webhook theo địa chỉ đã đăng ký trước đó
1. Register webhook
Webhook được đăng ký theo Topic. Có thể sử dụng cả Rest và GraphQl Api để đăng ký
- GraphQl
Link tài liệu tham khảo: https://shopify.dev/docs/api/admin-graphql/2023-04/objects/WebhookSubscription#field-webhooksubscription-topic
- REST
Link docs: https://shopify.dev/docs/api/admin-rest/2023-04/resources/webhook#post-webhooks
2. Webhook event topic:
Để đăng ký webhook, bạn cần lựa chọn 1 topic, có rất nhiều topic để bạn có thể đăng ký hầu như mọi sự thay đổi về sản phẩm, collections, orders, customer của store… đảm bảo dữ liệu của app và store luôn được đồng bộ một cách nhanh nhất.
- Xem tất cả các event topic mà shopify hỗ trợ tại đây : https://shopify.dev/docs/api/admin-rest/2023-04/resources/webhook#event-topics
3. Nhận và xử lý hook
Shopify gửi webhook cùng với header,payload đến endpoint đăng ký đã đăng ký.
- Verify the webhook: Mỗi yêu cầu webhook bao gồm header X-Shopify-Hmac-SHA256 được mã hóa base64, header này được tạo bằng app's client secret cùng với dữ liệu được gửi trong yêu cầu. Nếu bạn đang sử dụng PHP hoặc Ruby on Rails hoặc Sinatra, thì header là HTTP_X_SHOPIFY_HMAC_SHA256. So sánh giá trị được tính toán với giá trị trong header X-Shopify-Hmac-SHA256 hoặc HTTP_X_SHOPIFY_HMAC_SHA256. Nếu giá trị tiêu đề và thông báo HMAC khớp với nhau thì webhook được gửi từ Shopify.
- Update data in app theo webhook payload
- Response to the webhook: Endpoint nhận hook phải trả về status code 200. Bất kỳ response nào nằm ngoài phạm vi 200, kể cả mã chuyển hướng HTTP 3XX, đều cho biết rằng bạn không nhận được webhook. Shopify đợi 5 giây để phản hồi từng yêu cầu đối với webhook. Nếu không có phản hồi hoặc trả về lỗi, Shopify sẽ thử kết nối lại 19 lần trong 48 giờ tới. Nếu có 19 lần thất bại liên tiếp thì đăng ký webhook sẽ tự động bị xóa. Một cảnh báo rằng subscription sẽ bị xóa được gửi đến địa chỉ email khẩn cấp dành cho nhà phát triển của ứng dụng. Xem thêm: https://shopify.dev/docs/apps/webhooks/configuration/https
III. Những vấn đề thường gặp với Shopify webhook
Việc sử dụng webhook có ưu điểm là dữ liệu sẽ được đồng bộ liên tục với database của shopify, những thay đổi sẽ được cập nhật ngay và gần như không có delay time. Tuy nhiên nó cũng sẽ có những vấn đề lớn về database performance của app và dễ bị outdate dữ liệu nếu quá phụ thuộc. Một số vấn đề thường gặp :
- Spam webhook : Do việc đăng ký hook khá dễ dàng và lượng hook của 1 số topic đôi khi sẽ rất lớn, nên dễ dẫn đến quá tải cho hệ thống server nhận hook
- Read and write to the database a lot : Đặc biệt ở webhook product/update thì lượng request là cực kỳ lớn, bất cứ thay đổi nào của sản phẩm đều được bắn về server nhận, nếu không xử lý kỹ sẽ dễ gây quá tải database
- Remove subscription : Như đã đề cập ở trên, khi việc xử lý hook quá chậm hoặc bị fail quá nhiều lần, shopify sẽ tự động remove webhook đã đăng ký đó
- Outdated data : Việc phụ thuộc quá nhiều vào webhook dễ dẫn đến data ở app bị outdate so với store
IV. Những giải pháp giảm thiểu ảnh hưởng
Sau đây là cách mà mình đã áp dụng tại các dự án ở FireGroup, tuy chưa giải quyết được hoàn toàn và triệt để những vấn đề, nhưng nó cũng đã phần nào giúp việc xử lý webhook được dễ dàng và hiệu quả, ít gây ảnh hưởng đến server nhận hook.
1. Restrict spam
- Tách hẳn webhook ra riêng 1 service để tối ưu và hạn chế ảnh hưởng đến các service khác.
- Chỉ đăng ký webhook khi thật sự cần thiết. Chia nhỏ việc đăng ký webhook ra, theo từng tính năng cần thiết.
- Remove khi không cần dùng.
- Đăng ký payload muốn nhận để hạn chế dung lượng (dùng fields trong lúc register).
- Lưu hook vào cache để chạy dần theo schedule. Hạn chế được các webhook trùng lặp và chủ động được việc khi nào cần update data theo webhook.
2. Restrict update DB
- Kiểm tra trước xem dữ liệu hook vs DB có thay đổi k trước khi update.
- Có thể lưu thêm 1 lớp cache để compare thay vì select thẳng vào DB.
- Gom nhiều items lại để sử dụng bulk insert và bulk update.
- Tách những resource nặng và cần nhận nhiều hook ra DB riêng.
3. Restrict outdated data
- Thường xuyên theo dõi tình trạng webhook trong admin app insights
- Chạy tiến trình register hook định kỳ hàng tháng (hoặc quý) để update/remove hook cho user active
- Đối với các data quan trọng, cũng nên chạy tiến trình định kỳ để cập nhật thay vì dựa vào hook hoàn toàn Tham khảo biểu đồ trong app admin
V. Bài học thực tế từ sản phẩm
Ở Firegroup, chúng tôi có 1 dự án shopify app tên là Transcy. Vì đặc thù app là app dịch nên việc sử dụng webhook của shopify để update danh sách sản phẩm của store là điều rất cần thiết. Trong số các webhook mà chúng tôi sử dụng, có webhook product, bao gồm các topic product/create, product/update, product/delete, đây là những webhook cực kỳ nhiều request và tốn nhiều thời gian để xử lý nó.
Ban đầu team chỉ đơn thuần là đăng ký webhook và nhận xử lý chung với server của app. Cấu trúc của dự án sử dụng PHP/Laravel làm service api và Database Mysql để lưu dữ liệu sản phẩm. Tuy nhiên sau khi lượng user sử dụng app tăng lên nhanh chóng, chúng tôi đã phải đối mặc với cơn ác mộng về performance, lượng hook quá lớn lên đến cả trăm triệu request mỗi tháng đã làm sập gần như hoàn toàn hệ thống server của app. Để giải quyết bài toán trên team đã đưa ra nhiều pháp để improve và tối ưu hệ thống xử lý
- Đầu tiên team quyết định move việc xử lý webhook ra 1 cụm server riêng biệt, để tránh gây ảnh hưởng đến các phần khác trong app. Ở trường hợp xấu nhất, chỉ có cụm webhook die thì các chức năng của app vẫn hoạt động bình thường.
- Sau đó team tối ưu việc đăng ký hook, chỉ đăng ký những dữ liệu thật sự cần thiết cho tính năng trong app như product title, description, handle, content… và chỉ đăng ký khi user thật sự có thể sử dụng tính năng đó (khi user charge). Việc này có tác dụng giảm payload khi nhận hook, giảm tải cho server khá nhiều.
- Tuy nhiên các giải pháp trên chỉ có thể giảm tải cho hệ thống server api, phần quan trọng và bị ảnh hưởng nhiều nhất là Database thì vẫn thường xuyên bị overload CPU. Sau khi ngâm cứu về database và cách xử lý dữ liệu read/write nhiều, team nhận ra rằng đối với việc xử lý dữ liệu lớn như này thì Nosql sẽ có nhiều lợi thế hơn, nên mọi người đã move hoàn toàn việc lưu trữ dữ liệu product từ Mysql sang MongoDB, các phần khác của dự án vẫn chạy trên Mysql.
- Song song với việc chuyển đổi Database team cũng chuyển đổi cơ chế nhận hook từ việc nhận và xử lý trực tiếp sang hàng đợi xử lý. Khi server webhook nhận request, nó sẽ không xử lý ngay mà đẩy data hook đó lên Kafka, return 200 cho shopify, giảm được rất nhiều áp lực lên cụm server webhook. Lúc này Kafka sẽ đóng vai trò như một bộ nhớ đệm lưu trữ toàn bộ request hook từ shopify, cụm server hook chỉ có mỗi nhiệm vụ đẩy lên kafka và không làm gì thêm. Trong lúc đẩy lên kafka, team sẽ đánh dấu bằng product_id để ghi đè lên ở những lần sau. Nói nôm na là trong vòng 1 phút 1 product A có 5 request update thì trên kafka chỉ lưu đúng 1 lần (data sẽ là của lần cuối cùng), việc lưu đè như này sẽ hạn chế được việc phải xử lý dữ liệu trùng lặp nhiều lần của hook. Sau đó sẽ có 1 crontab chạy mỗi phút 1 lần để quét toàn bộ data trên kafka ra, xử lý lưu và DB và clear data đó.
- Việc xử lý data từ kafka sẽ được đưa vào các Queue sử dụng Worker được viết bằng Python để update từ từ vào Database, trước khi update, sẽ có bước query dữ liệu trong Database (có cache dữ liêu product cần check ở Redis trước) để check xem thật sự có thay đổi so với dữ liệu trong app không, nếu có mới update. Việc kiểm tra này sẽ giảm được rất nhiều việc Write Database, vì không phải bất cứ dữ liệu nào của sản phẩm cũng cần update (app chỉ cần update 1 vài trường của product như title, description, content….)
- Phía sau việc update product còn khá nhiều tiến trình xử lý nữa, tuy nhiên những improve trên cũng đã giảm khá nhiều những ảnh hưởng của webhook lên server và database của app. Hiện tại việc xử lý webhook của dự án Transcy đã khá ổn định, không còn gặp nhiều vấn đề như trước nữa, nhưng với việc user sử dụng app tiếp tục tăng, thì team cũng đang nghiên cứu thêm nhiều giải pháp mới để liên tục improve nó.
Trên đây là những khái niệm cơ bản và những cách tối ưu khi làm việc với shopify webhook. Hi vọng mọi người sẽ áp dụng được ngay vào những dự án shopify app của mình để đồng bộ nhanh chóng giảm thiểu rủi ro mà webhook mang lại.