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

    View full-size slide

  2. Flag-based feature management
    for a large scale application
    Munetoshi Ishikawa

    View full-size slide

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

    View full-size slide

  4. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  7. Feature Branch
    Example: git-flow, GitHub flow
    main branch
    (trunk, master, develop…)

    View full-size slide

  8. Feature Branch
    Example: git-flow, GitHub flow
    feature branch
    main branch
    (trunk, master, develop…)

    View full-size slide

  9. Feature Branch
    Example: git-flow, GitHub flow
    feature branch
    pull request
    main branch
    (trunk, master, develop…)

    View full-size slide

  10. Feature Branch
    Example: git-flow, GitHub flow
    feature branch
    pull request
    main branch
    (trunk, master, develop…)

    View full-size slide

  11. Case of Large Scale Application
    Lots of branches for a large scale application
    main branch
    feature branch

    View full-size slide

  12. 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

    View full-size slide

  13. 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

    View full-size slide

  14. 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!

    View full-size slide

  15. How to Resolve Conflicts 1/2
    Merge refactoring first
    feature development
    main branch

    View full-size slide

  16. How to Resolve Conflicts 1/2
    Merge refactoring first
    feature development
    main branch

    refactoring

    View full-size slide

  17. How to Resolve Conflicts 1/2
    Merge refactoring first
    feature development
    main branch

    refactoring
    resolve

    View full-size slide

  18. How to Resolve Conflicts 1/2
    Merge refactoring first
    Many branches are required to resolve conflicts
    feature development
    main branch

    refactoring
    resolve

    View full-size slide

  19. How to Resolve Conflicts 2/2
    Merge feature development first
    feature development
    main branch

    View full-size slide

  20. How to Resolve Conflicts 2/2
    Merge feature development first
    feature development
    main branch

    refactoring

    View full-size slide

  21. How to Resolve Conflicts 2/2
    Merge feature development first
    feature development
    main branch

    refactoring
    resolve

    View full-size slide

  22. How to Resolve Conflicts 2/2
    Merge feature development first
    feature development
    main branch

    refactoring
    resolve
    1. The conflict becomes large

    View full-size slide

  23. 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

    View full-size slide

  24. 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!

    View full-size slide

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

    View full-size slide

  26. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  29. 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

    View full-size slide

  30. Feature Flag Usage Example
    Case1: Replace presentation logic
    presenter =
    if (BuildConfig.FEATURE_STICKER) NewPresenter()
    else OldPresenter()


    View full-size slide

  31. 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

    View full-size slide

  32. 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

    View full-size slide

  33. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide

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

    View full-size slide

  40. 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)

    View full-size slide

  41. 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

    View full-size slide

  42. 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

    View full-size slide

  43. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  48. Feature Disabling 2/2
    Just turn off problematic features
    STICKER=ENABLED DISABLED

    problem found
    main branch
    release branch
    feature PR
    refactoring PR

    View full-size slide

  49. 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

    View full-size slide

  50. 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

    View full-size slide

  51. Topics
    ‣ Pain of feature branches
    ‣ Overview of feature flag
    ‣ Flag implementation
    ‣ Limitations and considerations

    View full-size slide

  52. Implementation Options
    ‣ Gradle plugin and BuildConfig
    ‣ Build variants
    ‣ Annotation processing
    ‣ Reflection

    View full-size slide

  53. Implementation Options
    ‣ Gradle plugin and BuildConfig
    ‣ Build variants
    ‣ Annotation processing
    ‣ Reflection

    View full-size slide

  54. 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

    View full-size slide

  55. 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)

    View full-size slide

  56. 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)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  59. IDE Warnings
    Warning for a conditional branch with a boolean
    constant
    if (BuildConfig.FEATURE_STICKER) {

    View full-size slide

  60. IDE Warning Workaround
    Call `Boolean.valueOf(...)` for a debug build

    val valueString =
    "Boolean.valueOf($value)"
    FeatureFlagPlugin.kt (Gradle plugin)

    View full-size slide

  61. 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)

    View full-size slide

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

    View full-size slide

  63. 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

    View full-size slide

  64. 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)

    View full-size slide

  65. Implementation of Control by Variants 2/2
    val buildTypeName = variant.buildType.name
    FeatureFlagPlugin.kt (Gradle plugin)

    View full-size slide

  66. 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)

    View full-size slide

  67. 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)

    View full-size slide

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

    View full-size slide

  69. 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

    View full-size slide

  70. 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

    View full-size slide

  71. 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

    View full-size slide

  72. Implementation of Control by Version
    val tokenizedFlag = flag.split(":since_")

    ...
    FeatureFlagPlugin.kt (Gradle plugin)

    View full-size slide

  73. Implementation of Control by Version
    val tokenizedFlag = flag.split(":since_")

    ...
    val sinceVersion = VersionNumber
    .parse(tokenizedFlag.getOrNull(1).orEmpty())
    FeatureFlagPlugin.kt (Gradle plugin)

    View full-size slide

  74. 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)

    View full-size slide

  75. Bonus Points for Control by Version
    Easy to know when we can clean up the flag
    STICKER=...:since_2.0
    main branch
    2.0

    View full-size slide

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

    View full-size slide

  77. Runtime Flag Modification
    Make flags overridable in a debug menu for
    - Demoing
    - Debugging
    - Side-by-side testing

    View full-size slide

  78. 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)

    View full-size slide

  79. 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)

    View full-size slide

  80. 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)

    View full-size slide

  81. 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)

    View full-size slide

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

    View full-size slide

  83. 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>",
    "FEATURE_FLAG_ACCESSOR_MAP",
    codeBuilder.toString()
    )
    FeatureFlagPlugin.kt (Gradle plugin, pseudocode)

    View full-size slide

  84. 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)

    View full-size slide

  85. 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)

    View full-size slide

  86. Topics
    ‣ Pain of feature branches
    ‣ Overview of feature flag
    ‣ Flag implementation
    ‣ Limitations and considerations

    View full-size slide

  87. 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

    View full-size slide

  88. 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

    View full-size slide

  89. Considerations for Release Testing
    ‣ Regression tests required for halfway feature
    ‣ Hard to guarantee the flag completeness
    - Might be revealed partially

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  98. Summary
    ‣ Unmanageable conflicts
    ‣ Good for release process / information sharing
    ‣ Extendible Gradle plugin implementation
    ‣ Feature flags are not silver bullets

    View full-size slide

  99. References
    ‣ GitLab flow
    - https://docs.gitlab.com/ee/workflow/gitlab_flow.html
    ‣ Trunk based development
    - https://trunkbaseddevelopment.com/

    View full-size slide

  100. Thank you for listening

    View full-size slide