Under Deconstruction: The State of Shopify's Monolith
Bài tóm tắt Shopify 2020 3. Quy mô lớn Độ khó: Trung bình
Shopify · 2020 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc
Bối cảnh
- 18 tháng sau Shopify 2019. Monolith chính có 2,8 triệu dòng Ruby, 500.000 commit, phát triển liên tục từ 2006, hàng trăm developer đang thêm tính năng.
- Mục tiêu ban đầu vẫn giữ (developer cảm thấy như đang làm trên một app nhỏ hơn nhiều), nhưng hầu hết chi tiết đã thay đổi.
Thay đổi chính (những gì đã sửa so với 2019)
- Đây là bài toán con người. Một team trung tâm không thể đi ngược đà của hàng trăm developer. Họ dùng mô hình hành vi của BJ Fogg (Ability, Motivation, Prompt):
- Ép pattern giao tiếp giữa component một cách đồng loạt phản tác dụng: thêm luật mà không giải bài toán thật; có nơi developer bẻ bài toán cho vừa pattern và làm codebase tệ đi.
- Họ chuyển sang tìm chỗ developer muốn sửa nhưng thấy quá khó, rồi tăng khả năng cho họ.
- Phong trào từ dưới lên: Architecture Guild (~400 thành viên, 54 buổi gặp), embed và pair với các team, Developer Handbook. Theo tác giả, nếu làm lại sẽ lập guild ngay từ đầu.
- Kiến trúc toàn diện, theo cặp thuộc tính:
- Đóng gói + đồ thị phụ thuộc đơn giản: đồ thị lúc đầu dày tới mức mỗi component phụ thuộc hơn một nửa số component khác, đầy vòng. Public interface mà không gỡ phụ thuộc chỉ là một lớp gián tiếp thêm vào. Cách sửa: inversion of control (pub/sub qua
ActiveSupport::Notifications), làmShopgần như rỗng, chia layer (Platform → Supporting → Frontend). - Loose coupling + high cohesion: đo bằng change locality, tức code thay đổi cùng nhau thì nằm cùng nhau; theo dõi số component mỗi PR chạm vào. Ưu tiên cohesion theo chức năng hơn cohesion theo bảng dữ liệu (thói quen Rails).
- SOLID ở mức class là nền cho thiết kế tốt ở mức component.
- Đóng gói + đồ thị phụ thuộc đơn giản: đồ thị lúc đầu dày tới mức mỗi component phụ thuộc hơn một nửa số component khác, đầy vòng. Public interface mà không gỡ phụ thuộc chỉ là một lớp gián tiếp thêm vào. Cách sửa: inversion of control (pub/sub qua
- Công cụ:
- Component chuyển thành Rails Engines.
- Contract ở ranh giới dùng Sorbet (thay thư viện schema tự viết).
- Wedge bị bỏ: call graph lúc chạy quá nhiễu, mơ hồ, mất hơn 1 giờ. Thay bằng Packwerk: phân tích tham chiếu hằng số tĩnh, chạy vài phút, gắn vào PR để chặn vi phạm trước khi merge.
- Hiện trạng 2020: 37 component; Packwerk áp dụng cho khoảng 1/3. Mục tiêu dài hạn là đồ thị không vòng và test suite riêng cho từng component.
Vì sao (khi nào mới tách service)
Shopify chỉ tách service khi có lý do rõ ràng:
- Storefront rendering: chỉ đọc, throughput rất cao, scale khác hẳn trang quản trị.
- Credit card vaulting: dữ liệu nhạy cảm không được chảy qua phần khác.
Bài học cho SWE
- Thay đổi kiến trúc lớn luôn dừng ở trạng thái dở dang lâu hơn bạn nghĩ. Hãy chọn cách tiếp cận theo trạng thái trung gian nào hữu ích nhất:
- Vẽ ranh giới theo tầm nhìn rồi làm chặt dần: có ngay quyền sở hữu rõ ràng, nhưng ranh giới yếu. Shopify chọn cách này cho monolith chính.
- Tách vài component sạch rồi lớn dần: có ví dụ tốt, nhưng phần còn lại vẫn là một khối hỗn độn.
- Công cụ phân tích tĩnh và nhanh (chạy trong PR) hữu ích hơn phân tích động chính xác nhưng chậm.
- Quyền sở hữu rõ ràng giúp chia nhỏ các việc toàn codebase (nâng Rails, phân loại exception).
- Vẫn còn vướng: GraphQL vòng, Elasticsearch phụ thuộc Active Record xuyên component.
Liên quan
Được nhắc tới trong
Công ty (1)