owned this note
owned this note
Published
Linked with GitHub
# 快速迭代:如何使用 feature toggle 來開發佈署應用程式 - Aki wang Noah hsu
{%hackmd @DevOpsDay/B1cnefOI0 %}
#### 》[議程介紹](https://devopsdays.tw/2024/session-page/3098)
#### 》[填寫議程滿意度問卷|回饋建言給辛苦的講者](https://forms.gle/66hToGrb1V7hL5gV9)
> 共筆從這開始
### Prerequisite
- Docker Engine: 23+
- Java: JDK 11+ (recommand 17)
- sdk and sample code: [line/Flagship4j](https://github.com/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](https://martinfowler.com/articles/feature-toggles.html)
- 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](https://github.com/line/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](https://github.com/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](https://github.com/line/Flagship4j)
2. install library
```
./gradlew publishToMavenLocal
```
3. start a new spring boot application (with web and lombok)
or use the example in the downloaded project
```groovy
implementation "com.linecorp.flagship4j:flagship4j-openfeature-spring-boot-starter:0.1.40-SNAPSHOT"
```
4. create a controller with openfeature client
5.
5. create flag setting on the openflagr UI (release toggle)
6. trun toggle to permission togggle (constraints by `role`)
7. add `MutableContext` as a third parameter when call client.getBooleanValue()
### throw Exception
```java
@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
```java
@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
```java
@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
```java
@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");
}
}
```