owned this note changed a year ago
Linked with GitHub

快速迭代:如何使用 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
手機版請點選上方 按鈕展開議程列表。

議程介紹

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

簡報連結

共筆從這開始

tags: DevOpsDays Taipei 2024

Prerequisite

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

Demo step

  1. 下載專案
    sdk and sample code: line/Flagship4j
    (也可以看看專案中的其他 examples)

1.1 run flagr

docker compose up -d

access in http://localhost:18000

  1. add code snippet in ./examples/openfeature-spring-boot-example/build.gradle
dependencies {
  ...
  implementation "org.springframework.boot:spring-boot-starter-web"
  ...
}
  1. remove code from OpenFeatureSpringBootExampleApplication
 implements CommandLineRunner 
 
 public void run(String... args) throws Exception {
 ...}
  1. add a class LayoutController

throw Exception

@RestController
public class LayoutController {

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

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

}

run app by IDE or command

./gradlew :examples:openfeature-spring-boot-example:bootRun

test it

curl --location -i 'http://localhost:8080/layouts/product'

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-product-enabled", false, context)) {
            return ResponseEntity.ok(buildLayoutProduct());
        }

        return ResponseEntity.notFound().build();

    }

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

}

test it

curl --location -i 'http://localhost:8080/layouts/product' \
-H "x-user-name:aki"

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-product-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