快速迭代:如何使用 feature toggle 來開發佈署應用程式 - Aki wang Noah hsu

歡迎來到 DevOpsDay Taipei 2024 共筆

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

共筆入口:https://hackmd.io/@DevOpsDay/2024
手機版請點選上方 按鈕展開議程列表。

議程介紹

填寫議程滿意度問卷|回饋建言給辛苦的講者

共筆從這開始

Prerequisite

  • Docker Engine: 23+
  • Java: JDK 11+ (recommand 17)
  • sdk and sample code: line/Flagship4j

feature toggle

因為使用多個branch做開發,有時候有些branch太早開發,但太晚merge進去,中間會有很多其他小開發完成,這樣就會產生merge conflict。
這時候就要去處理這樣的問題
但feature toggle可以有相關的解方。

feature切換

常常button換顏色的時候要換來換去,這樣也很苦惱

feature toggle 優勢與特色

  • Invisibility
    • 沒有enable的話code是看不到的
  • Speed
    • 加速開發週期
    • 先merge什麼都沒做的source code,再merge做了一半的source code)
  • Experimentation
    • 體驗性,例如切換不同顏色的button,也可以設定權限)
    • 可以做很多切換體驗不同的效果
  • Safety
    • 程式碼安全,可以快速關閉跟開啟功能
  • Live Update
    • 可以快速的real time將流量導過去,讓影響降低到最低。

toggle type

  • release toggles
    • 開發到一半的功能先關閉,開發完之後再出現
    • 大概幾週的時間去處理
  • ops toggle
    • 某些功能比較複雜,可能會造成程式的 crash,先關掉也不會造成影響
    • 可能會留在上面比較長的時間,驗證更改前後的狀況,比較效能上面的問題。
  • experiment toggles
    • 將user去分群給予不同畫面去體驗處理。
  • permission toggles
    • 今天可能去驗證身份,給予不同的權限去看不同的介面

是第二個好方法,最終要去盡量避免

"feature toggle is second best solution. the best solution is to find a way to gradually intergrate without feature branches and feature toggles" by Martin Fowler

Flagship4j

LINE Taiwan 開發的,已經開源出來了

open feature

有三個特點:

  1. one SDK, any backend
    • 今天開 interface 出來,
    • 統一一個介面,去使用 feature toggle
  2. support your favorite tools
  3. speak your language
    • 多種語言

openFlagr

AB testing,用go來撰寫的。performance非常的好。

Prerequisite

  • Docker Engine: 23+
  • Java: JDK 11+ (recommand 17)
  • sdk and sample code: line/Flagship4j
tags: DevOpsDays Taipei 2024

第一個是未完成Code 的 Merege 問題
第二個是新需求的Adopt,新舊功能都留著

Invisibility
Speed
Experimentation
Safety
Live Update

Realse Toggle : 承上

Ops Toggles : 流量大有另外函數

Permission Toggles : 權限控制

Experiment Toggles : 使用者體驗

Toggle remove

toggle測試結束的時候最終要拿掉,不然會累積太多很難處理,會把code跟toggle feature一起拿掉。

testing

基本上會希望非開發人員可以去處理,QA或是project owner可以去改變跟測試

Demo step

  1. 下載專案
    sdk and sample code: line/Flagship4j

  2. install library

./gradlew publishToMavenLocal
  1. start a new spring boot application (with web and lombok)
    or use the example in the downloaded project
implementation "com.linecorp.flagship4j:flagship4j-openfeature-spring-boot-starter:0.1.40-SNAPSHOT"
  1. create a controller with openfeature client

  2. create flag setting on the openflagr UI (release toggle)

  3. trun toggle to permission togggle (constraints by role)

  4. add MutableContext as a third parameter when call client.getBooleanValue()

throw Exception

@RestController
@RequiredArgsConstructor
public class LayoutController {

    private final Client flagrClient;

    @GetMapping("/layouts/product")
    public ResponseEntity<?> getLayoutProduct() {
        return ResponseEntity.ok(buildLayoutProduct());
    }

    private Map<?, ?> buildLayoutProduct() {
        throw new UnsupportedOperationException("Not implemented yet");
    }

}

On / Off with not found

@RestController
@RequiredArgsConstructor
public class LayoutController {

    private final Client flagrClient;

    @GetMapping("/layouts/product")
    public ResponseEntity<?> getLayoutProduct() {
        if (flagrClient.getBooleanValue("layout-prdocut-enabled", false)) {
            return ResponseEntity.ok(buildLayoutProduct());
        }

        return ResponseEntity.notFound().build();
    }

    private Map<?, ?> buildLayoutProduct() {
        return Map.of("btnBuyColor", "blue", "btnBuyRadius", "8px", "btnBuyPos", "page");
    }

}

WhiteList

@RestController
@RequiredArgsConstructor
public class LayoutController {

    private final Client flagrClient;

    @GetMapping("/layouts/product")
    public ResponseEntity<?> getLayoutProduct(@RequestHeader(name = "x-user-name", required = false) String userName) {
        MutableContext context = new MutableContext();
        if (userName != null && userName.equals("aki")) {
            context.add("entityContext", Structure.mapToStructure(Map.of("role", "ROLE_QA")));
        }

        if (flagrClient.getBooleanValue("layout-prdocut-enabled", false, context)) {
            return ResponseEntity.ok(buildLayoutProduct());
        }

        return ResponseEntity.notFound().build();

    }

    private Map<?, ?> buildLayoutProduct() {
        return Map.of("btnBuyColor", "blue", "btnBuyRadius", "8px", "btnBuyPos", "page");
    }

}

A/B Testing

@RestController
@RequiredArgsConstructor
public class LayoutController {

    private final Client flagrClient;

    @GetMapping("/layouts/product")
    public ResponseEntity<?> getLayoutProduct(@RequestHeader(name = "x-user-name", required = false) String userName) {
        MutableContext context = new MutableContext();
        if (userName != null && userName.equals("aki")) {
            context.add("entityContext", Structure.mapToStructure(Map.of("role", "ROLE_QA")));
        }

        if (flagrClient.getBooleanValue("layout-prdocut-enabled", false, context)) {
            String theme = flagrClient.getStringValue("layout-product-theme", "themeA", new MutableContext(userName));

            switch (theme) {
                case "themeA":
                    return ResponseEntity.ok(buildThemeALayoutProduct());
                case "themeB":
                    return ResponseEntity.ok(buildThemeBLayoutProduct());
                default:
                    return ResponseEntity.notFound().build();
            }
        }

        return ResponseEntity.notFound().build();

    }

    private Map<?, ?> buildThemeALayoutProduct() {
        return Map.of("btnBuyColor", "blue", "btnBuyRadius", "8px", "btnBuyPos", "page");
    }

    private Map<?, ?> buildThemeBLayoutProduct() {
        return Map.of("btnBuyColor", "green", "btnBuyRadius", "0px", "btnBuyPos", "bottomNav");
    }

}
Select a repo