Upgrade to Pro — share decks privately, control downloads, hide ads and more …

巨大なアプリ開発を支えるフラグ管理術 / Flag-based feature management

巨大なアプリ開発を支えるフラグ管理術 / Flag-based feature management

Munetoshi Ishikawa (石川宗寿)
LINE Corporation Senior Software Engineer

DroidKaigi 2019
https://droidkaigi.jp/2019/timetable

LINE Developers

February 08, 2019
Tweet

More Decks by LINE Developers

Other Decks in Programming

Transcript

  1. ڊେͳΞϓϦ։ൃΛࢧ͑Δϑϥά؅ཧज़
 Flag-based feature management for a large scale application Abstract

    in three lines: 1. Branches are hard to manage in a large application 2. Enables/disables features in a property file 3. Implemented as a Gradle plugin Slides: in English
 Talk: in Japanese
  2. What I’d Like to Say For a large scale application

    ‣ Hard to manage lots of feature branches ‣ Hard to conduct “large” refactoring
  3. What I’d Like to Say For a large scale application

    ‣ Hard to manage lots of feature branches ‣ Hard to conduct “large” refactoring Go with feature flags, no more feature branches
  4. Topics ‣ Pain of feature branches ‣ Overview of feature

    flags ‣ Flag implementation ‣ Limitations and considerations
  5. Topics ‣ Pain of feature branches ‣ Overview of feature

    flags ‣ Flag implementation ‣ Limitations and considerations
  6. Case of Large Scale Application Lots of branches for a

    large scale application main branch feature branch
  7. What’s the Problem? Conflicts, conflicts, conflicts… - Commonly used code

    updated, like an API/library - Project wide refactoring, like package renaming feature development main branch …
  8. What’s the Problem? Conflicts, conflicts, conflicts… - Commonly used code

    updated, like an API/library - Project wide refactoring, like package renaming feature development main branch … refactoring
  9. What’s the Problem? Conflicts, conflicts, conflicts… - Commonly used code

    updated, like an API/library - Project wide refactoring, like package renaming feature development main branch … refactoring Conflict!
  10. How to Resolve Conflicts 1/2 Merge refactoring first Many branches

    are required to resolve conflicts feature development main branch … refactoring resolve
  11. How to Resolve Conflicts 2/2 Merge feature development first feature

    development main branch … refactoring resolve
  12. How to Resolve Conflicts 2/2 Merge feature development first feature

    development main branch … refactoring resolve 1. The conflict becomes large
  13. How to Resolve Conflicts 2/2 Merge feature development first feature

    development main branch … refactoring 1. The conflict becomes large 2. No chance to resolve with “all” branches resolve
  14. How to Resolve Conflicts 2/2 Merge feature development first feature

    development main branch … refactoring 1. The conflict becomes large 2. No chance to resolve with “all” branches resolve Conflict!
  15. How to Minimize the Conflicts Apply each “halfway” update to

    each other - Merge before finishing the implementation - Merge before fixing the release plan
  16. How to Minimize the Conflicts Apply each “halfway” update to

    each other - Merge before finishing the implementation - Merge before fixing the release plan Guard a “halfway” feature by a boolean flag
  17. Topics ‣ Pain of feature branches ‣ Overview of feature

    flags ‣ Flag implementation ‣ Limitations and considerations
  18. Feature Flag Concept ‣ Merge any pull request to the

    main branch main branch pull request
  19. Feature Flag Concept ‣ Merge any pull request to the

    main branch ‣ Enable/disable a feature with a property file main branch pull request flag property file FEATURE_STICKER=ENABLED FEATURE_THEME=DISABLED
  20. Feature Flag Usage Example Case1: Replace presentation logic presenter =

    if (BuildConfig.FEATURE_STICKER) NewPresenter() else OldPresenter()

  21. Feature Flag Usage Example Case1: Replace presentation logic presenter =

    if (BuildConfig.FEATURE_STICKER) NewPresenter() else OldPresenter()
 Case2: Change view visibility button.isVisible = BuildConfig.FEATURE_THEME
  22. What a Feature Flag Realizes ‣ Minimizes conflicts ‣ Separates

    release schedule from the development progress ‣ Disables features if critical issues found on the release branch ‣ Shares the halfway features to other members
  23. What a Feature Flag Realizes ‣ Minimizes conflicts ‣ Separates

    release schedule from the development progress ‣ Disables features if critical issues found on the release branch ‣ Shares the halfway features to other members
  24. Conflict Minimization Apply a project wide update to halfway features

    main branch feature PR refactoring PR Conflict
  25. Conflict Minimization Apply a project wide update to halfway features

    - Forget about ongoing refactoring once a pull request is merged main branch feature PR refactoring PR Conflict saved PRs
  26. What a Feature Flag Realizes ‣ Minimizes conflicts ‣ Separates

    release schedule from the development progress ‣ Disables features if critical issues found on the release branch ‣ Shares the halfway features to other members
  27. Release schedule Feature PRs can be merged before creating target

    release branch main branch release branch 1.0 2.0
  28. Release schedule Feature PRs can be merged before creating target

    release branch main branch release branch 1.0 2.0 feature “STICKER” PRs (for 2.0)
  29. Release schedule Feature PRs can be merged before creating target

    release branch main branch release branch 1.0 2.0 feature “STICKER” PRs (for 2.0) STICKER=DISABLED STICKER=ENABLED
  30. Release schedule Feature PRs can be merged before creating target

    release branch main branch release branch 1.0 2.0 feature “STICKER” PRs (for 2.0) STICKER=DISABLED STICKER=ENABLED
  31. What a Feature Flag Realizes ‣ Minimizes conflicts ‣ Separates

    release schedule from the development progress ‣ Disables features if critical issues found on the release branch ‣ Shares the halfway features to other members
  32. Feature Disabling 1/2 Hard to revert after resolving conflict main

    branch release branch problem found feature branch
  33. Feature Disabling 1/2 Hard to revert after resolving conflict main

    branch release branch refactoring PR problem found feature branch
  34. Feature Disabling 2/2 Just turn off problematic features problem found

    main branch release branch feature PR refactoring PR
  35. Feature Disabling 2/2 Just turn off problematic features STICKER=ENABLED DISABLED

    → problem found main branch release branch feature PR refactoring PR
  36. What a Feature Flag Realizes ‣ Minimizes conflicts ‣ Separates

    release schedule from the development progress ‣ Disables features if critical issues found on the release branch ‣ Shares the halfway features to other members
  37. Halfway Feature Sharing ‣ Code is visible for other developers

    - For a related or dependent feature development ‣ A feature is deployable for a demo and testing - For project owners, testers, and designers
  38. Topics ‣ Pain of feature branches ‣ Overview of feature

    flag ‣ Flag implementation ‣ Limitations and considerations
  39. Key Idea of Implementation 1. Create a Java property file

    2. Write the property content to BuildConfig FEATURE_STICKER=true
 FEATURE_THEME=false feature_flag.properties
  40. Key Idea of Implementation 1. Create a Java property file

    2. Write the property content to BuildConfig Properties()... .forEach { (key, value) -> variant .buildConfigField("boolean", "$key", "$value") } FeatureFlagPlugin.kt (Gradle plugin)
  41. Key Idea of Implementation 1. Create a Java property file

    2. Write the property content to BuildConfig public static final boolean FEATURE_STICKER = true; public static final boolean FEATURE_THEME = false; BuildConfig.java (output)
  42. Requirements ‣ IDE warnings workaround ‣ Flag control by build

    variants ‣ Flag control by release version ‣ Runtime flag modification
  43. Requirements ‣ IDE warnings workaround ‣ Flag control by build

    variants ‣ Flag control by release version ‣ Runtime flag modification
  44. IDE Warnings Warning for a conditional branch with a boolean

    constant if (BuildConfig.FEATURE_STICKER) {
  45. IDE Warning Workaround Call `Boolean.valueOf(...)` for a debug build 


    val valueString = "Boolean.valueOf($value)" FeatureFlagPlugin.kt (Gradle plugin)
  46. IDE Warning Workaround Call `Boolean.valueOf(...)` for a debug build (Use

    a literal for release to help ProGuard) val isRelease = variant.buildType.name == "release"
 val valueString = if (isRelease) "$value" else "Boolean.valueOf($value)" FeatureFlagPlugin.kt (Gradle plugin)
  47. Requirements ‣ IDE warnings workaround ‣ Flag control by build

    variants ‣ Flag control by release version ‣ Runtime flag modification
  48. Flag Control by Build Variants Enable with debug while disable

    with release # Enabled only for debug FEATURE_STICKER=debug # Enabled for both of debug and release FEATURE_THEME=release feature_flag.properties
  49. Implementation of Control by Variants 1/2 const val DEBUG_NAME =

    "debug" const val RELEASE_NAME = "release" val BUILD_TYPE_TO_FLAG_LIST_MAP = mapOf( DEBUG_NAME to arrayOf(DEBUG_NAME, RELE...), RELEASE_NAME to arrayOf(RELEASE_NAME) ) FeatureFlagPlugin.kt (Gradle plugin)
  50. Implementation of Control by Variants 2/2 val buildTypeName = variant.buildType.name

    val availableFlagValues = BUILD_TYPE_TO_FLAG_LIST_MAP[buildTypeName] .orEmpty() FeatureFlagPlugin.kt (Gradle plugin)
  51. Implementation of Control by Variants 2/2 val buildTypeName = variant.buildType.name

    val availableFlagValues = BUILD_TYPE_TO_FLAG_LIST_MAP[buildTypeName] .orEmpty() val isEnabled = availableFlagValues.contains(flagValue) // WHERE, flagValue is "debug" or "release" FeatureFlagPlugin.kt (Gradle plugin)
  52. Requirements ‣ IDE warnings workaround ‣ Flag control by build

    variants ‣ Flag control by release version ‣ Runtime flag modification
  53. Flag Control by Release Version 1/2 May forget to enable

    before creating the target release branch main branch release branch 1.0 2.0 feature “STICKER” PRs (for 2.0) STICKER=debug STICKER=release
  54. Flag Control by Release Version 1/2 May forget to enable

    before creating the target release branch main branch release branch 1.0 2.0 feature “STICKER” PRs (for 2.0) STICKER=debug STICKER=release
  55. Flag Control by Release Version 2/2 Reserve target release version

    as soon as release schedule fixed main branch release branch 1.0 2.0 feature “STICKER” PRs (for 2.0) STICKER=debug,release:since_2.0
  56. Implementation of Control by Version val tokenizedFlag = flag.split(":since_")
 ...

    val sinceVersion = VersionNumber .parse(tokenizedFlag.getOrNull(1).orEmpty()) FeatureFlagPlugin.kt (Gradle plugin)
  57. Implementation of Control by Version val tokenizedFlag = flag.split(":since_")
 ...

    val sinceVersion = VersionNumber .parse(tokenizedFlag.getOrNull(1).orEmpty()) availableFlagValues.contains(buildTypeName) && sinceVersion <= project.applicationVersion FeatureFlagPlugin.kt (Gradle plugin)
  58. Bonus Points for Control by Version Easy to know when

    we can clean up the flag STICKER=...:since_2.0 <delete flag> main branch 2.0
  59. Requirements ‣ IDE warnings workaround ‣ Flag control by build

    variants ‣ Flag control by release version ‣ Runtime flag modification
  60. Runtime Flag Modification Make flags overridable in a debug menu

    for - Demoing - Debugging - Side-by-side testing
  61. Implementation of Flag Modification 1/2 `buildConfigField` outputs `static final` (I

    don’t like Java’s reflection. Especially, sun.misc.Unsafe) variant.buildConfigField( "boolean", "$key_DUMMY", "$value " ) FeatureFlagPlugin.kt (Gradle plugin)
  62. Implementation of Flag Modification 1/2 `buildConfigField` outputs `static final` (I

    don’t like Java’s reflection. Especially, sun.misc.Unsafe) variant.buildConfigField( "boolean", "$key_DUMMY", "$value; " ) FeatureFlagPlugin.kt (Gradle plugin)
  63. Implementation of Flag Modification 1/2 `buildConfigField` outputs `static final` (I

    don’t like Java’s reflection. Especially, sun.misc.Unsafe) variant.buildConfigField( "boolean", "$key_DUMMY", "$value; public static boolean $key = $value" ) FeatureFlagPlugin.kt (Gradle plugin)
  64. Implementation of Flag Modification 1/2 `buildConfigField` outputs `static final` (I

    don’t like Java’s reflection. Especially, sun.misc.Unsafe) public static final boolean FEATURE_STICKER_DUMMY = true; public static boolean FEATURE_STICKER = true; BuildConfig.java (output)
  65. Implementation of Flag Modification 2/2 Make overridable flag accessible without

    reflection - Create a flag list with supplier/consumer pair
  66. Implementation of Flag Modification 2/2 Make overridable flag accessible without

    reflection - Create a flag list with supplier/consumer pair val codeBuilder = StringJoiner("\n", "new HashMap<>(){{", "}}") flags.forEach { key -> codeBuilder .add("""put("$key", new Pair<>(() -> $key, (b) -> $key = b));""") } variant.buildConfigField( "Map<String, Pair<Supplier, Consumer>>", "FEATURE_FLAG_ACCESSOR_MAP", codeBuilder.toString() ) FeatureFlagPlugin.kt (Gradle plugin, pseudocode)
  67. Implementation of Flag Modification 2/2 Make overridable flag accessible without

    reflection - Create a flag list with supplier/consumer pair public static final Map<...> FEATURE_FLAG_ACCESSOR_MAP = new HashMap<>() {{ put( "FEATURE_STICKER", ) ... BuildConfig.java (output, pseudocode)
  68. Implementation of Flag Modification 2/2 Make overridable flag accessible without

    reflection - Create a flag list with supplier/consumer pair public static final Map<...> FEATURE_FLAG_ACCESSOR_MAP = new HashMap<>() {{ put( "FEATURE_STICKER", new Pair<>( () -> FEATURE_STICKER, (b) -> FEATURE_STICKER = b) ) ... BuildConfig.java (output, pseudocode)
  69. Topics ‣ Pain of feature branches ‣ Overview of feature

    flag ‣ Flag implementation ‣ Limitations and considerations
  70. Appropriate Cases of Feature Flags ‣ Easy to apply a

    feature flag - Replace a class, layout file, or value ‣ Hard to apply a feature flag - Update API, library, or SDK version ‣ Enough to use a branch - Make a prototype or trial implementation
  71. Considerations for Pull Requests ‣ Keep each pull request small

    ‣ Think about the order/structure of pull requests eg., create a skeleton class → implement the logic
  72. Considerations for Release Testing ‣ Regression tests required for halfway

    feature ‣ Hard to guarantee the flag completeness - Might be revealed partially
  73. Summary ‣ Pain of feature branches ‣ Overview of feature

    flag ‣ Flag implementation ‣ Limitations and considerations
  74. Summary ‣ Pain of feature branches ‣ Overview of feature

    flag ‣ Flag implementation ‣ Limitations and considerations
  75. Summary ‣ Unmanageable conflicts ‣ Overview of feature flag ‣

    Flag implementation ‣ Limitations and considerations
  76. Summary ‣ Unmanageable conflicts ‣ Overview of feature flag ‣

    Flag implementation ‣ Limitations and considerations
  77. Summary ‣ Unmanageable conflicts ‣ Good for release process /

    information sharing ‣ Flag implementation ‣ Limitations and considerations
  78. Summary ‣ Unmanageable conflicts ‣ Good for release process /

    information sharing ‣ Flag implementation ‣ Limitations and considerations
  79. Summary ‣ Unmanageable conflicts ‣ Good for release process /

    information sharing ‣ Extendible Gradle plugin implementation ‣ Limitations and considerations
  80. Summary ‣ Unmanageable conflicts ‣ Good for release process /

    information sharing ‣ Extendible Gradle plugin implementation ‣ Limitations and considerations
  81. Summary ‣ Unmanageable conflicts ‣ Good for release process /

    information sharing ‣ Extendible Gradle plugin implementation ‣ Feature flags are not silver bullets