polpiellaDEV

May 14, 2021

Clean Architecture: A Feature Flag Manager

7 minute read

In this blog post we will be looking at how to build a Feature Flag Manager in a clean, scalable and reusable way using clean architecture and SOLID design principles.

This blog post was inspired by John Sundell's post on how to make an Analytics service and Essential Developer's playlist on Clean Architecture. I run through how I would go about implementing a Feature Flag Manager system that allows you to turn certain features in your app on or off.

Requirements

Your company is growing very quickly and, as it does, a bunch of new features are getting tested and rolled out every week. A great part of this process will involve experimentation and A/B testing to make sure that the content and features you are deploying are providing the right value to your users. When you start refining the work ready to be picked up, you get the following requirements:

Architecture

Now that the requirements are clear, it's time to think about how we're going to implement it. The step of going away and drawing a diagram is very important as it sets the foundation for the system you will be working on and allows you to start coding with a very clear idea in mind. I tend to use draw.io to come up with UML diagrams that explain the whole system in as much detail as I can.

I like to do this step side-by-side with an XCode playground open so that I can, at the same time, put in practice some of the things I have come up with and see what improvements I can make. In the next few sections I will walk you through what the layers for our system will look like.

Providers and Manager

The first layer that we want to implement will consist of every single provider that holds and retrieves feature flags. These SDKs or BE services can come in many different shapes and forms but the concepts, as we explained in the requirements will be the same. Also, it is worth noting that these SDKs will have a bunch of other methods that we don't really care about, and we want to only expose what is needed by the modules that require the Feature Flag module as a dependency. There are a couple of SOLID principles here that we need to keep in mind when designing this layer:

How can we solve this? Abstraction! In Swift, it is particularly easy to abstract types, as we can simply create a protocol and make each of our providers conform to it.

FeatureFlagManager.swift
protocol FeatureFlagManager {
    func variation<T: FeatureFlag>(for flag: T) -> T.T
}

protocol FeatureFlag {
    associatedtype T: Decodable

    var key: String { get }
    var defaultValue: T { get }
}

Here we go! We now extend all of our providers to conform to this protocol and, since all of them take different types, we can pass in our own custom FeatureFlag abstract implementation that can then be made concrete on our FE modules.

User Authentication

As mentioned in our requirements section, and because the providers will return values based on user attributes, we need to pass the User object to the providers. Now, how can we do this? The first instinct would be to add a method to the FeatureFlagManager called setUser that we can then extend each provider with, but that approach would not be the most suitable one? Why? Let's revisit some SOLID principles again:

Thankfully, and as it is usually the case, the answer is very simple. We just need to create another abstraction that our providers can extend:

FeatureFlagUserAuthenticator.swift
protocol FeatureFlagUserAuthenticator {
    setUser(_ user: User?)
}

struct User {
    let id: String
    let countryCode: String
    let segments: [String]
}

Client Implementation

The last requirement we need to implement is to integrate this abstraction as a dependency to all our modules and write concrete implementations of the FeatureFlag abstractions for the variations we want get. For this, I opted to have a struct per module only containing the flags that the module needs. Let's look at Settings as an example:

SettingsFeatureFlag.swift
struct SettingsFeatureFlag<T: Decodable>: FeatureFlag {
    let key: String
    let defaultValue: T

    static func newProfile() -> SettingsFeatureFlag<Bool> {
        SettingsFeatureFlag<Bool>(key: "new-user-profile",
                                    defaultValue: false)
    }

    static func updatedSettingsUI() -> SettingsFeatureFlag<Bool> {
        SettingsFeatureFlag<Bool>(key: "updated-settings-ui",
                                    defaultValue: false)
    }

    static func enabledCountries() -> SettingsFeatureFlag<[String]> {
        SettingsFeatureFlag<[String]>(key: "settings-content",
                                        defaultValue: ["es", "en"])
    }
}

In order to do this, we just need to use dependency injection in our modules and make them depend on the FeatureFlagManager or FeatureFlagUserAuthenticator depending on their needs.

For example, the settings and feed modules only need to get variations, so they will only have to depend on FeatureFlagManager while our Login an Registration modules will need to only depend on FeatureFlagUserAuthenticator:

ViewModels.swift
// MARK: - SettingViewModel

class SettingsViewModel {
    let featureFlagManager: FeatureFlagManager

    init(featureFlagManager: FeatureFlagManager) {
        self.featureFlagManager = featureFlagManager
    }

    func getEnabledCountries() -> [String] {
        featureFlagManager
            .variation(for: SettingsFeatureFlag<[String]>.enabledCountries())
    }
}

// MARK: - LoginViewModel

class LoginViewModel {
    let featureFlagUserAuthenticator: FeatureFlagUserAuthenticator

    init(featureFlagUserAuthenticator: FeatureFlagUserAuthenticator) {
        self.featureFlagUserAuthenticator = featureFlagUserAuthenticator
    }

    func userDidLogIn(_ user: User) {
        featureFlagUserAuthenticator.setUser(user)
    }

    func userDidLogOut() {
        featureFlagUserAuthenticator.setUser(nil)
    }
}

A final overview

Finally, I wanted to share the UML diagram where it all started 😅. I find that diagrams are particularly useful when it comes to software design and architecture and let you see a lot clearly what you're trying to achieve. Finished architecture UML diagram showing our Feature Flag system. As we can see in the image above, our modules don't know the concrete implementation of the providers, which can easily be replaced and our design is compliant with SOLID principles.

Next Steps

There is still some functionality missing such as tracking events as we need a way of letting our tracking provider know of how many conversions we're having on each particular feature flag. I will write a follow-up article but how would you go about implementing that? Where you would add the functionality?

Happy Coding!