Feature Toggles

Nam Vu
22 min readDec 10, 2021

Feature Toggles (hay còn gọi là Feature Flags) là một kỹ thuật giúp cho hệ thống đang chạy có thể thay đổi được mà không cần thay đổi mã nguồn (code) trong quá trình runtime. Kỹ thuật này là một tập hợp các pattern giúp chúng ta có thể triển khai những tính năng mới tới người dùng (users) nhanh và an toàn hơn.

Thật ra Feature Toggles đã luôn ở đâu đó trong việc phát triển phần mềm từ rất lâu rồi, đôi khi chúng ta cũng đã làm qua, nhưng lại ít khi để ý là nó cũng được tổng hợp và phát biển thành một lý thuyết trong việc phát triển phần mềm.

Bài viết này sẽ đề cập tới các lợi ích của nó thông qua các usecase cụ thể , sau đó chúng ta sẽ đi sâu hơn các pattern và best practices để thành công khi thực hành nó. Bài viết sẽ đi từ ví dụ đơn giản nhất và sau đó sẽ đào sâu thêm.

Feature Toggles là gì?

Đầu tiên ta hãy đi vào một usecase rằng chúng ta đang phát triển một game mô phỏng thành phố (Sim city?) và bạn có nhiệm vụ phải tối ưu thuật toán Spline Reticulation trong lõi (core) mô phỏng của chương trình. Và nó là một cuộc đại tu (refactor) khá lớn trong hệ thống và phải mất ít nhất một vài tuần để thực hiện. Trong khi đó các thành viên khác cũng phải tiếp tục công việc khác của họ trên “codebase” của hệ thống. Và tất nhiên chúng ta không muốn rẽ nhánh (branching) trong codebase, bởi vì dựa trên những kinh nghiệm trước đó rằng việc đó sẽ rất kinh tởm :v (painful) khi phải merge những long-lived branches với nhau. Thay vào đó tất cả vẫn sẽ làm việc trên một “trunk-based”, và những ai làm việc tối ưu thuật toán Spline Reticulation trong core sẽ sử dụng Feature Toggle để cách ly phần việc đó ảnh hưởng tới các tính năng khác và làm ổn định hệ thống trong quá trình phát triển.

Thật ra để làm điều này rất đơn giản là tạo một biến flag để on/off tạm thời tính năng mới.

Ví dụ function tính toán Spline Reticulation như sau

Các ví dụ sẽ được viết bằng JavaScript ES2015, và có hơi hướng functional programing và có sử dụng kỹ thuật truyền một method khác vào một method, bạn đọc nào quen với OOP nên chú ý để hiểu được ví dụ.

Sau khi ứng dụng biến flag

Hệ thống sẽ vẫn chạy với thuật toán cũ oldFashionedSplineReticulation với một Toggle Point là biến useNewAlgorithm và nếu ai đó muốn làm việc với thuật toán mới thì chỉ đơn giản un-comment dòng useNewAlgorithm = true;

Biến flag động — có thể thay đổi khi runtime

Sau khi thuật toán mới hoàn thành giai đoạn phát triển (development) và sẵn sàng cho việc tích hợp và integration tests. Yêu cầu bây giờ là làm sao chúng ta có thể enable/disable thuật toán mới trong lúc hệ thống run time, có nghĩa là ta sẽ phải bỏ đi biến flag ngớ ngẩn useNewAlgorithm trong code đi.

Giải pháp cũng khá đơn giản là sử dụng một biến config khác được lưu ở in-memory như Redis và cung cấp một giao diện (UI) để operator on/off. Ví dụ ở dưới back-end ta sẽ cung cấp một function để thay đổi biến config featureConfig từ Admin UI.

Dưới function reticulateSplines thay vì dùng biến flag thì sẽ call tới configuration động bên trên.

Chúng ta sẽ tạo ra một “toggle router” dựa trên default configuration đọc từ một config file, tuy nhiên chúng ta vẫn có thể thay đổi cấu hình dựa trên đối số truyền vào từ Admin UI.

Ví dụ với một integration testing.

Sẵn sàng để release.

Khi team đã hoàn thành và sẵn sàng với thuật toán Spline Reticulation mới và mong muốn đưa thuật toán đó vào thử nghiệm trên “production” với một lượng nhỏ người dùng trước, vì dù sao thuật toán Spline Reticulation và một phần rất quan trọng của hệ thống. Team sẽ quyết định thử nghiệm các tính năng cho các user nội bộ (internal user) trước và cách làm sẽ như sau:

+ Toggle Router sẽ lấy hướng dẫn bật tắt tính năng từ Toggle Configuration (biến môi trường) cho từng môi trường, chỉ mặc định bật tính năng mới trên môi trường pre-production

+ Toggle Configuration có thể thay đổi được trong lúc hệ thống runtime từ Admin UI.

+ Toggle Router cũng có thể đưa ra quyết định bật tắt tính năng mới dựa trên Toggle Context. Đó là một context sử dụng để thực hiện các logic cho việc quyết định on/off tính năng. Ví dụ như Toggle Context sẽ dựa trên một cookie hay một HTTP Header. Thông thường Toggle Context được sử dụng như một proxy để nhận request từ người dùng.

(Đừng lo lắng nếu bạn đọc chưa hiểu gì, chúng ta sẽ bàn thêm tiếp ở bên dưới bài viết :D)

Nhờ khái niệm về Toggle Router và Toggle Context mà team có thể test tính năng mới mà không cần một môi trường Test riêng biệt và cũng không ảnh hưởng tới hệ thống hiện tại. Và team cũng có thể chạy tính năng mới trên môi trường production chỉ cho internal user, ví dụ như bằng cách đọc từ một cookie đặc biệt. Team có thể tự set cookie đó cho chính họ và verify tính năng mới trên môi trường Production.

Các chiến thuật release/testing ứng dụng Feature Flags

Canary releasing

Team nhận thấy sau khi verify thuật toán Spline Reticulation trên Prod với chính họ (internal user), hệ thống chạy khá ổn. Team quyết định sẽ thận trọng tiếp tục thử nghiệm tính năng đó cho một số lượng nhỏ user được lựa chọn (canary user) trước khi release nó cho toàn bộ user của họ, vì dù sao thuật toán đó là một phần rất quan trọng trong hệ thống. Chiến thuật đó gọi là “Canary Release”.

Team tiếp tục cải tiến Toggle Router để nhận diện được 1% users sẽ được sử dụng thuật toán mới bằng cách lấy ngẫu nhiên 1% người dùng bằng cách sử dụng phép toán modulo dựa trên user ID. Thuật toán mới chỉ hoạt động trên 1% người dùng và 99% còn lại vẫn dùng thuật toán cũ, các số liệu kinh doanh vẫn được monitor trên cả hai đối tượng user để chắc chắn rằng thuật toán mới không làm ảnh hưởng xấu tới trải nghiệm của user. Sau khi chắc chắn rằng mọi thứ đều ổn với hệ thống mới, team có thể nâng tiếp số % người dùng hoặc enable thuật toán mới trên toàn bộ user.

A/B Testing

Team kinh doanh cảm thấy khá ấn tưởng với những gì team product đã làm với việc release thuật toán mới. Trong khi đó nội bộ team kinh doanh cũng có một vài tranh luận về những ý tưởng của các thành viên, bọn họ đã quyết định sử dụng nhưng basic concept về Feature Flags để thực hiện một vài A/B Testing. Họ sẽ tạo ra những biến thể (variant) của hệ thống và sẽ thử nghiệm trên một tập hợp các người dùng (challenger). Từ những số liệu về hành vi của người dùng, team kinh doanh sẽ đưa ra được quyết định đúng đắn nhất. Từ đó họ sẽ có cái nhìn khách quan hơn

Các chiến lược Toggles

Release Toggles

Với những dự án sử dụng “trunk-based” developement với Continuous Delivery thì Feature Flags giúp cho team có thể phát triển liên tục các tính năng đang dang dở (in-progress features) mà không cần rẽ nhánh (branching), từ đó sản phẩm có thể release liên tục lên production bất chấp code-based có chứa những đoạn code chưa hoàn chỉnh (incomplete code) và chưa kiêm thử (un-tested codepaths). Định nghĩa đó được goi là Release Toggles.

Từ đó các PM (product manager) có thể uyển chuyển với các chiến lược (strategy) trong kinh doánh, ví dụ trong việc enable các tính năng đặc biệt chỉ cho từng đối tác hay tập user nào đó. Hay những tính năng chưa hoàn chỉnh nhưng đủ đáp ứng chỉ cho một vài đối tác mà chưa sẵn sàng cho các đối tác khác sử dụng. Hoặc một usecase khác PM có thể linh hoạt bật/tắt các tính năng chỉ phục vụ trong các chiến dịch marketing (marketing campaign) cụ thể nào đó (săn sale 1/11 …)… Các chiến lược đó thường được gọi là các Release Toggles.

Tuy nhiên các Release Toggles chỉ nên tồn tại tối đa là 2 tuần, qua thời gian đó một là chúng ta biến nó thành một tính năng chính chạy ổn định lâu dài với hệ thống hoặc là bỏ nó đi, để code-base trở thành sạch hơn (clean code). Nếu hệ thống quá lạm dụng Release Toggles và Feature flag thì không những code-base mà cả các tính năng (feature) của hệ thống sẽ trở nên cực kỳ khó để kiếm soát, chả mấy mà nó sẽ trở thành một hỗn độn :P

Experiment Toggles

Experiment Toggles là một khái niệm khác mà chúng ta đã nói qua trong phần A/B testing. Mỗi tập người dùng trong hệ thống (cohort users) sẽ được đưa vào một nhóm được chỉ định để thử nghiệm các biến thể (variant) khác nhau của hệ thống. Có nghĩa là lúc này hệ thống sẽ chạy xong xong các variant cho từng nhóm user, từ đó sẽ thu thập các dữ liệu về hành vi từ đó phân thích được ảnh hưởng của từng variant tới với user. Experiment Toggles thường được sử dụng để tối ưu hóa trải nghiệm người dùng trong quay trình payment trong các hệ thống, ví dụ như các payment wall (payment pop-up) trong các hệ thống báo điện tử, hoặc luồng thanh toán đơn hàng trong các hệ thống ecommerce.

Experiment Toggles thường tồn tại lâu hơn Release Toggles để thu thập đủ dữ liệu để đưa ra những kết quả đáng tin nhất. Tùy vào lượng traffic mà thời gian nó tồn tại (lifetime) sẽ từ vài giờ tới vài tuần. Tuy nhiên cũng không nên lâu quá, vì có thể những thay đổi khác của hệ thống (những tính năng mới …) trong thời gian đó có thể thay đổi kết quả của thử nghiệm.

So với Release Toggles thì tính thay đổi động (dynamic) của Experiment Toggles sẽ cao hơn, ngoài việc có thể thay đổi lúc runtime (changes with runtime re-configuration) mà nó có thể thay đổi trên từ request của end users, như trong hình trên.

Ops Toggles

Khác với Release/Experiment Toggles là tập chung control các feature ảnh hưởng tới người dùng (end user), thì Ops Toggles là một khái niệm lại tập chung vàp việc control các feature có thể gây ảnh hưởng tới hiệu xuất (performance) của hệ thống. Điều này để giúp các nhà quản trị hệ thống (operator) có thể can thiệp để disable tính năng đó trên Production nếu cần.

Phần lớn các Ops Toggles tồn tại tương đối ngắn trước khi nó sẽ được gỡ bỏ việc gắn cờ. Tuy nhiên cũng không có gì lạ nếu trong hệ thống có một lượng nhỏ các Ops Toggles tồn tại khá lâu (long-lived Ops Toggles), mục đích để Operator có thể tắt một vài tính năng không quan trọng (non-critical) khi hệ thống đang chịu tải cao một cách bất thường. Ví dụ Operator có thể quyết định tắt tính năng “Recommendations” trên trang chủ của một hệ thống e-Commerce khi hệ thống đang chịu tải cao trên môi trường production. Các long-lived Ops Toggles có thể được coi là một Circuit Breaker hoạt động “bằng cơm” :D

Như đã đã nói hầu hết các flags đều có vòng đời ngắn, nhưng có một vài flags có thể gần như được giữ lại vô thời hạn, việc đó cho phép các Operator có thể phản ứng nhanh chóng với việc bật/tắt flags để vô hiệu hóa một vài tính năng như đề cập bên trên bằng cách re-config trong lúc hệ thống runtime.

Permissioning Toggles

Permissioning Toggles cũng gần giống với Experiment Toggles, tuy nhiên nó không được dùng để thử nghiệm A/B test, mà là dùng để cung cấp các tính năng cao cấp (premium features) chỉ cho những người dùng đã trả phí (premium user), hoặc những những tính năng ở giai đoạn Beta được enable cho những đối tượng user mà họ mong muốn trải nghiệm sớm. Ví dụ MP3 Zing, người dùng trả phí sẽ được enable tính năng nghe nhạc chất lượng cao (lossless) và không phải hiện thị quảng cáo. Hay ví dụ người dùng có thể đăng ký trải nghiệm sớm các bản iOS mới đang trong giai đoạn Beta.

Với việc quản lý các premium features danh cho các premium users với Permissioning Toggles thì các tất cả các state trong nó gần như cũng là vô thời hạn, đối với cái Toggles thì Permissioning Toggles có vòng đời lâu nhất, có thể dài tới vài năm hoặc hết cả vòng đời của sản phẩm. Và với việc chỉ enable cho từng request của user thì nó cũng là một Toggles có khả năng thay đổi động (dynamic) nhất.

Phân loại các loại Toggle khác nhau

Chà có vẻ chúng ta có khá nhiều các chiến lược Toggles khác nhau với vòng đời và độ thay đổi động của state. Tuy nhiên tựu chung lại ta có có thể phân loại các Toggles về hai loại chính đó là “static” & “dynamic” toggles và “Long-lived” toggles vs “transient” toggles

Static vs dynamic toggles

Về cơ bản để thay đổi flags trong các Toggle thì có 3 kiểu chính là

1. Thay đổi trong lúc deployment cho từng môi trường (changes with a deployment)

2. Thay đổi lúc hệ thống runtime với việc re-configuration lại các flags.

3. Việc thay đổi sẽ dựa trên từng request của users.

Hai chiến thuật đầu tiên dựa vào việc đọc từ các static toggle trong các toggle configuration (dạng file data config), để On/Off các flags cho từng tính năng trong lúc deployment (file config) hoặc lúc hệ thống runtime (data config). Nhưng cơ bản nó sẽ ăn với toàn bộ các user trên hệ thống, cho nên có thể xếp nó vào loại “static toggles”.

Còn chiến thuật thứ 3 được xếp vào loại “dynamic toggles” vì nó dựa và từng request của user chứ không dựa vào các toggle configuration. Ví dụ như việc tính toán dựa trên user’s ID như trong Experiment Toggle, hay dựa trên user’s type (premium user or not) như trong Permissioning Toggles (cohorting algorithm)

Chúng ta sẽ tìm hiểu sâu hơn về việc quản lý các toggle configuration ở phần sau bài viết.

Long-lived toggles vs transient toggles

Sự khác nhau khác giữa các chiến thuật Toggles đó là khoảng thời gian nó tồn tại trong hệ thống. Có thể nó chỉ tồn tại trong vài giờ hoặc vài ngày hay vài tuần nhưng cũng có loại Toggles tồn tại tới vài năm hay tồn tại mãi mãi trong vòng đời sản phẩm. Sự khác biệt đó sẽ ảnh hưởng tới việc chúng ta triển khai các Toggle Point như thế nào. Nếu Release Toggle chỉ tồn tại tối đa vài ngày (transient toggles) thì việc implement các Toggle Point chỉ đơn giản là một mệnh đề if/else đơn giản trên Toggle Router như ví dụ ở đầu bài viết

Tuy nhiên với các Toggles tồn tại trong một thời gian dài (long-lived toggles) như Permissioning Toggle thì tốt hơn hết chúng ta không nên viết bừa bãi những mệnh đề if/else trong các Toggle Points, điều này khiến code-base sẽ trở nên rối rắm và khó bảo trì. Tốt nhất chúng ta hãy tiếp cận những kỹ thuật triển khai dễ bảo trì hơn (maintainable implementation techniques)

Các kỹ thuật triển khai Feature Toggles

Như đề cập bên trên thì các Feature Flags trong code-base là tập hợp các Toggle Point, và số các Toggle Point sẽ tăng dần theo tỉ lệ thuận với các Feature Flags mà hệ thống cung cấp. Nếu không có những design-pattern cho việc triển khai thì chẳng mấy mà codebase chúng ta sẽ trở thành một đống hỗn độn (spaghetti code). Sau đây chúng ta sẽ tìm hiểu về một vài các design-pattern khi thiết kế các Feature Flags

Tách (decouple) các Toggle Point ra khỏi logic đằng sau nó.

Một lỗi phổ biến khi thiết kế các Feature Flags là dính (couple) việc check Flags (kiểm tra tính năng có được bật hay không) với logic đằng sau nó. Ví dụ chúng ta phát triển mộ hệ thống thương mai điện tử mới (next-gen-ecomm) và trong đó chúng ta phát triển một tính năng mới cho phép việc hủy đơn hàng thông qua việ click vào link trong invoice email. Và chúng ta cũng sử dụng một Toggle Point để quản lý việc bật/tắt tính năng này trên production như sau:

Có nghĩ là trong khi thực hiện việc generate invoice email, hệ thống sẽ kiểm tra liệu tính năng “next-gen-ecomm” có được bật hay không, nếu có thì sẽ thêm một đoạn nội dung hủy đơn hàng vào email.

Nhìn thì có vẻ hợp lý đấy, nhưng thực tế đoạn code trên vi phạm SOLID rồi, làm như vậy đơn giản chỉ là một tricky (thủ thuật) không hơn không kém. Tại sao tính năng tạo invoice email lại phải quan tâm tới logic của tính năng mới hủy đơn hàng trong email? Rõ ràng nó đã vi phạm tính Single-responsibility trong SOLID. Hay điều gì xảy ra nếu chúng ta muốn bật một tính năng khác của bộ next-gen-ecomm mới mà không muốn lộ tính năng hủy đơn hàng trong invoice email? Hoặc ngược lại? Hay làm thế nào để chúng ta chỉ muốn triển khai tính năng hủy đơn hàng chỉ có một số người dùng nhất định ? (Experiment Toggles). Và làm thế nào để quản lý khi số lượng Toggle Point ngày càng tăng trong code-base?

Cách giải quyết là tách (decouple) các Toggle Point (logic check xem tính năng có được bật hay không) ra khỏi logic đằng sau nó. Việc này giúp tập trung logic của Toggle Point ra một nơi khác để việc thay đổi sau này sẽ tập trung một nơi giúp cho việc maintain code-base dễ hơn. Ví dụ

Logic của Toggle Point ra một method riêng

Trong tính năng generate invoice email sẽ refactor như sau:

Chúng ta sẽ tạo ra một FeatureDecisions Object chưa toàn bộ các logic của Toggle Point của một hoặc nhiều decision method cho từng Toggle Point cụ thể , trong tình hướng này là logic của việc thêm link hủy order trong invoice email.

Giờ thì chúng ta đã tách logic của Toggle Point ra khỏi loigc đằng sau nó và tập chung một nơi. Và bất cứ khi nào chúng ta muốn sửa đổi logic của Toggle Point thì giờ chỉ cần một nơi duy nhất để sửa đổi.

Giải pháp này có vẻ ổn hơn tuy nhiên pattern này chỉ phù hợp với các Static Toggles và chưa phù hợp với các Dynamic Toggles, ví business mong muốn thay đổi việc thử nghiệm việc thêm link hủy order vào invoice email theo kiểu A/B testing. Chúng ta hãy tiếp tục tìm hiểu một pattern khác ngay sau đây để giải quyết vấn đề trên.

Đảo ngược quyền quyết định (Inversion of Decision)

Ở ví dụ trước về cơ bản nó vẫn chưa SOLID, tính năng generate invoice email vẫn phải dính tới (coupled) vào module FeatureDecisions. Về cơ bản chúng ta vẫn chưa thực sự Single responsibility trong tính năng đó, và nó cũng khiến cho việc testing (Unit Test) khó khăn hơn. Khi hệ thống có nhiều Feature flag hơn thì sẽ càng nhiều các module mà nó phải gắn tới (coupled).

Để giải quyết vấn đề này (coupling issues) chúng ta có thể sử dụng tới nguyên lý thiết kế Inversion of Control. Chúng ta có thể tách (decouple) Feature Flag ra khỏi tính năng invoice emailer của chúng như như sau:

FeatureDecisions sẽ được truyền từ bên ngoài vào với đại diện là biến “config”

Bây giờ rõ ràng thì thay vì InvoiceEmailer phải quan tâm quản lý và khởi tạo FeatureDecisions thì chúng ta sẽ đưa ra nó ngoài khỏi InvoiceEmailer thông qua một input đầu vào của method là biến “config”, điều này sẽ khiến việc test generateInvoiceEmail() dễ dàng hơn, ví dụ ta sẽ có một Unit Test sau có thể test cả việc email có chưa nội dung hủy đơn hàng (cancellation content) hay không?

Ngoài ra chúng ta cũng có thể tạo ra một Factory method để tập chung (centralization) việc khởi tạo đối tượng vào một mối, bằng cách sử dụng nguyên lý thiết kế Dependency Injection như sau:

Tránh mệnh đề điều kiện trong code

Với ví dụ trên thì Toggle Point được implement với một mệnh đề if/else, nó chỉ phù hợp với transient toggles (short-lived toggle), túc là nó sẽ được xóa khỏi codebase trong thời gian ngắn tới. Tuy nhiên nó sẽ rất xấu xí nếu nó là một long-lived toggle, giải pháp sẽ là chúng ta sẽ sử dụng Strategy pattern để bỏ mệnh đề if/else ra khỏi tình năng generate invoice email như sau:

Như thấy bên trên chúng ta đã apply Strategy pattern để cho phép tính năng invoice emailer của chúng ta đã loại bỏ mệnh đề if/else và có thể được cấu hình (config) với một function là additionalContentEnhancer để giúp việc thêm những content khác vào email (như link cancel order).và factory FeatureAwareFactory sẽ có nhiệm vụ sử dụng featureDecisions chọn một strategy để khởi tạo invoice email. Nếu featureDecisions nói rằng tính năng gửi link hủy invoice order được bật, nó sẽ sử dụng một enhance function là addOrderCancellationContentToEmail truyền vào method createInvoiceEmailler để thêm nội dung mong muốn trong email. Còn nếu không thì chúng ta chỉ truyền vào một method identityFn, tác dụng của nó là chẳng làm gì cả chỉ giữ nguyên content gốc của email :P.

Toggle Configuration

Bên trên chúng ta đã đi qua (walk through) về các loại Toggle, mỗi loại Toggle lại có các cách config khác nhau, và dưới đây chúng ta cũng sẽ tổng hợp và phân loại lại các loại Toggle

Dynamic routing vs dynamic configuration

Nhắc lại một chút thì về cơ bản ta có hai cách cấu hình (configuration) một Toggle đó là dựa vào các file cấu hình trong lúc deploy (dynamic configuration), hoặc các config có thể thay đổi lúc runtime (dynamic routing).

Trong dynamic routing thì chúng ta lại có hai cách mà flags có thể thay đổi trung lúc runtime như sau:

- Một là: flags có thể cấu hình động (dynamically re-configured) từ On sang Off trên toàn hệ thống để đáp ứng với một sự cố hệ thống nào đó (Ops Toggle). Toggles kiểu này có các quyết định (decisions) cấu hình khá động (dynamic) nhưng lại cấu hình (config) khá là tĩnh (static), bởi vì nó dựa vào file config trong lúc deployment, có nghĩa là ta chỉ có thể thay đổi decisions bằng cách re-deployment hệ thống.

- Hai là: flags có thể được cấu hình động dựa trên từng request của users giống cái cách Permissioning Toggles hay Experiment Toggles làm. Cách này thì chúng ta không cần phải thay đổi bất kỳ config hay thông số hệ thống (parametter)

Static configuration

Static configuratic là việc quản lý Toggle Config bằng static config file trên source code, có nghĩa là chúng ta có thể quản lý nó cùng với mã nguồn (source code) của hệ thống. Việc này thường được lựa chọn (prefer) bởi vì nó triển khai khá là đơn giản, bởi vì nó sẽ được thay đổi cùng theo Continuous Delivery pipeline giống như cách code của hệ thống được thay đổi theo từng môi trường (dev/qc/pre-prod/prod). Vì nó chỉ thay đổi theo CD cho nên trong quá trình runtime nó sẽ không được thay đổi, việc này cũng giảm effort cho việc test cho cả việc Toggle On hoặc Off trên hệ thống. Một lợi ích nhỏ khác là chúng ta có thể thấy được lịch sử toggle configuration trong từng bản release trước đó, do vậy ta có thể dễ dàng rollback lại các bản release trước đó nếu cần.

Static configuration về cơ bản nó rất đơn giản nhưng điểm hạn chế của nó khá là tĩnh (static), chúng ta không thể thay đổi nó khi runtime hoặc một vài yêu cầu khác động hơn ví dụ như trong Ops Toggles, nơi yêu cầu việc thay đổi nó động (dynamic hơn). Bên dưới chúng ta sẽ tìm hiểu dần các cách tiếp cận từ đơn giản (kém dynamic) tới phức tạp hơn (nhưng dynmaic hơn)

Mặc dù cấu hình tĩnh được ưu tiên hơn, nhưng có những trường hợp chẳng hạn như Ops Toggles, nơi yêu cầu một cách tiếp cận năng động hơn. Chúng ta hãy xem xét một số tùy chọn để quản lý cấu hình chuyển đổi, từ các cách tiếp cận đơn giản nhưng kém năng động cho đến một số cách tiếp cận rất phức tạp nhưng đi kèm với nhiều phức tạp.

Hardcoded Toggle Configuration

Các đơn giản nhất và không thể đơn giản hơn để thay đổi Feature Flag là comment/uncomment những đoạn code (block of code) cũ và mới

Đây là cách thay đổi kiểu hardcoding và không hỗ trợ việc dynamic re-configuration, nó chỉ phù hợp với việc chúng ta làm việc với hệ thống sử dụng chiến thuật branching (branching strategies) để quản lý thay đổi của code. Nó không thể áp dụng được nếu chúng ta đang áp dụng trunk-base và nó cũng khó khăn trong lên kịch bản testing (testing scenarios).

Parameterized Toggle Configuration

Còn một cách khác khá đơn giản để có thể thay đổi cấu hình các Feature Flags là chúng ta sẽ thay đổi thông qua command-line arguments hoặc environment variables truyền vào lúc start hệ thống. Thực ra cách làm việc đã tồn tại khá lâu trước khi khái niệm Feature Flag hay Feature Toggling ra đời, những ai làm việc với những hệ thống những năm 2000 chắc hẳn cũng khá quen rồi :D. Tuy nhiên nó cũng đi kèm với những hạn chế như cực kỳ khó khăn khi ta phải thay đổi config trên quá nhiều các node, hay mỗi thay đổi phải yêu cầu re-deploy hoặc phải restart lại các node

Toggle Configuration File

Một cách phổ biến khác được áp dụng cho Toggle Configuration là những thay đổi được lưu vào nhưng config file tập chung. Việc này có lợi ích là chúng ta có thể thống nhất các config vào chung một nơi và chỉ phải thay đổi tại các file config thay vì phải thay đổi code rồi re-build lại cả hệ thống. Tuy nhiên nó vẫn đòi hỏi hệ thống phải re-start lại.

Toggle Configuration in App DB

Việc sử dụng các config file tĩnh (static) có thể trở nên phức tạp khi hệ thống scale lên một quy mô nhất định với việc hệ thống đòi hỏi nhiều cụm máy chủ. Việc thay đổi này sẽ trở nên thách thức cho việc đảm bảo việc Toggle Configuration được nhất quán trên mọi node chạy trên nhiều cụm máy chủ (cluster). Việc này có thể được giải quyết bằng cách lưu các config trên một cơ sở dữ liệu tập trung (centralized store), đi kèm theo đó là chúng ta phải phát triển một admin UI để quản trị hệ thống (system operators), tester hay product managers có thể xem và thay đổi các Feature Flags và các config của nó.

Distributed Toggle Configuration

Việc sử dụng DB để lưu thông tin cầu hình của Feature Flags nó khá phổ biến, tuy nhiên ngày nay việc các sử dụng các key-value stores lại phù hợp hơn để lưu Feature Flags, ví dụ như sử dụng Zookeeper hay Consul để quản lý và lưu các cầu hình của hệ thống. Các dịch vụ này tạo ra một cụm phân tán (distributed cluster) để cung cấp các cấu hình hệ thống cho các nodes trong cluster đó. Cấu hình có thể được sửa đổi động bất cứ khi nào được yêu cầu và tất cả các nodes trong cluster đều được thông báo về việc thay đổi tức thì.

Một số hệ thống kiểu này (ví dụ như Consul) được đi kèm với một admin UI để người dùng quản trị có thể quản lý Toggle Configuration một giao diện tập chung duy nhất.

Overriding configuration

Cho tới nay thì tất cả các chiến thuật Toggle Configuration đều dựa trên một cơ chế duy nhất đó là dựa trên các cấu hình (config file). Thực tế với nhiều hệ thống phức tạp hơn, với các cấu hình có thể được ghi đè (override) từ nhiều ngườn khác nhau. Những ghi đè đó có thể đơn giản như một tệp cấu hình bổ xung (additional configuration file) hoặc phức tạp hơn như một cụm Zookeeper. Tuy nhiên nó sẽ đi ngược lại với tư tưởng của Continuous Delivery là các cấu hình phải được giống nhau chính xác trong suốt quá trình delivery pipeline.

Ví dụ ta có thể override configuration bằng một thông số cookie đặc biệt, thông qua query parameter hoặc dựa trên HTTP Header trên từng request. Việc override kiểu này nó cũng rất thuận tiện cho việc testing trên môi trường Production mà không sợ ảnh hưởng tới các users khác.

Nhược điểm của cách tiếp cận này là nếu một end users nào đó tò mò nào đó có thể tìm ra cách để bật tắt các Flags, việc này có thể bị lộ các tính năng mà bạn chưa muốn công khai. Chúng ta có thể mã hóa những tín hiệu override để phòng chống việc này, tuy nhiên nó cũng kéo theo việc hệ thống trở nên phức tạp hơn.

Thận trọng trong việc quản lý các Feature Toggles.

Hiện nay Feature Flags ngày càng được sử dụng rộng rãi hơn, đặc biệt từ khi nó được giới thiệu lần đầu tiên. Do các tính hữu ích và dễ phát triển từ đó các team business thường có xu hướng “nghiện” việc sử dụng nó, chính vì vậy các Feature Flags thường càng ngày càng được tạo ra nhiều hơn, từ đó câu chuyện về vận hành (operation) cũng như bảo trì (maintenance) trở nên phức tạp hơn. Trong codebase sẽ phải yêu cầu thêm các lớp trừu tượng (abstractions) hay những conditional logic (if/else), điều đó sẽ tạo ra những gánh nặng không nhỏ cho việc kiểm thử phần mềm. Sai lầm trị giá 460 triệu dollar của Knight Capital Group là một cảnh báo về những gì có thể xảy ra khi chúng ta không thể quản lý nổi các Feature Flag của mình một cách chính xác.

Tốt nhất rằng chúng ta nên giới hạn số lượng Feature Toggles ít nhất có thể, để giữ cho số feature flags có thể quản lý được. Chúng ta nên xóa các feature flags không còn dùng và không cần thiết nữa, hoặc cố gắng hạn chế số lượng A/B Testing (Experiment Toggles) và các tính năng ẩn khỏi hệ thống (Release Toggles) đang được triển khai cùng lúc. Hoặc một số đội nhóm còn đưa vào việc quản lý Feature Toggles thành các quy tắc (rule) trong việc triển khai hệ thống, ví dụ như sẽ luôn có một công việc (task) là review và loại bỏ các feature flags không cần thiết. Hay thậm chí đặt ra các giới hạn (limit) các feature flags được phép có trong hệ thống, và nếu ai đó muốn thêm các feature flags mới thì bắt buộc phải xóa bớt các feature flags hiện có. Hoặc cực đoan hơn nữa là nếu số lượng Feature Flags vượt quá limit thì Unit Test sẽ báo fail và ngăn chặn hệ thống được deploy và release.

Nguồn: https://martinfowler.com/articles/feature-toggles.html

--

--