Using Azure App Configuration for Remote Config with Android
September 22, 2019
11 minute read
Sadly, there is no library for Android Java.
Fortunately, we can fix that! Let’s take a look at how I did it.
Creating an App Configuration resource
First step is to create an App Configuration resource. Log on to the Azure portal, create yourself a resource group, then create the App Configuration resource - just like any other resource within Azure. You’ll have to give it a name and select a region, but that’s it.
Now, let’s put some data within the parameter store. To do this:
Select your App Configuration resource
Select Configuration explorer
Enter a key and a value. If you want (and I suggest it), also give them a label. I’m using production for my label.
Then click Apply
Repeat the create loop for as many settings as you want.
Once done, select Access keys, then select Read-only keys.
Make a note of the Endpoint, Id, and Secret.
I’ve created a couple of settings here:
Configure the Android project
First step is to ensure that the code we write will be able to access the settings necessary to connect to the remote configuration service. To do this, I’ve added them to the strings.xml file:
The actual values will come from the Access keys page of your resource. I’ve also created a settings data class to hold these values so they are easier to pass around:
To communicate with the backend resource, I’m going to use OkHttp. The configuration comes back with just one call (unless you have a large number of settings - in which case, you need to think a little bit more about the process of remote configuration). I don’t need to communicate with a large number of REST endpoints, so Retrofit (as an example) is overkill in this case.
I’ve added the following to my dependencies:
The current version of OkHttp is 4.2.0.
Create the App Configuration Service
As always when communicating with a remote service, I always create a service class that does the final leg of communication with the service. The methods in the service class correspond 1:1 with the REST methods on the service. I also delegate all the “protocol” level stuff (like authorization, logging, required headers) to an interceptor to make the code for the service class as simple as possible.
In this case, I have an AzureAppConfigurationService as follows:
We can look at this in two sections:
During initialization, I read the settings I placed into the strings.xml file and set up the HTTP client. Interceptors construct a pipeline between the request (the thing you do) and the eventual communication, allowing you to adjust the request and response along the way. In this case, there are two interceptors - one for doing all the requirements for Azure App Configuration, and one for logging the request and response. I’m using the standard logging interceptor to send the request/response to Timber for the logging.
The only endpoint I care about is the one that reads the entire configuration. I read all the settings configured that are labelled a specific way (in this case, production). This allows me to have different configuration sets for production vs. test vs. debug (or production vs. development). It turns the response into a key-value map and returns it. If anything goes wrong, it throws an error.
This is a pretty standard call, but where did I get the information from? Azure does a really good job of explaining all their REST calls by publishing the Swagger. This is the first place I look when discovering what a service can actually do. In this particular case, however, the App Configuration team has published a series of explanatory documents as well. I’m using the key-values document to determine what needs to be done. It says if I do a GET /kv?label=<something>, then I’ll get a JSON document back with an element items that contains an array of items. Those items have a key, value, etag (to determine if it has changed), date, etc. I’m only interested in the current key-value pair, so I extract those and return them as a map.
If anything goes wrong in the decoding, I just throw an error. Aside from being logged, we’ll see how that gets trapped later on.
The special App Configuration interceptor
Every single service on the Internet has a method of authenticating the user and authorizing operations. This is codified for App Configuration in their authentication document. I don’t like to have this detail embedded with every single request, so I abstract it into an interceptor. Let’s go through the code:
The main method here is the intercept method. It is the only method that is required to be an interceptor. In this particular version, we are altering the request object for every single request that comes through. We need some headers, which are added automatically. There are two special headers:
x-ms-date is a copy of the Date header and must be included.
x-ms-content-sha256 is the Base64-encoded SHA256 hash of the body of the request.
In our case, we don’t have a body, so I compute the hash of the empty string. Then, I need to compute the Authorization header. This is a HMAC-SHA256 signature of a specific string, using the Id and Secret of the resource to compute the signature. The computation is covered (in several languages - just not Kotlin) in the authentication.md file. I like the Kotlin computation - it feels easier. The only gotcha is that the header values must be in the same order as the signed headers list, and the code enforces this.
Your app should not cease to function if the App Configuration service goes down, and there are many reasons you may not be able to reach the App Configuration service - throttling, network issues, and service issues all come into the picture.
If, for whatever reason, we cannot reach the service, we can use a cached version of the configuration. For this purpose, I’ve got a higher level configuration service:
This is a singleton object, which needs to call LocalConfigurationStore.initialize(context) to retrieve the configuration into memory. It constructs it by:
Reading the configuration from the cached config.json file. It won’t exist the first time through.
Reading from remote configuration, overwriting the local configuration.
If remote configuration was successful, then write out the cached config.json version.
If remote configuration (i.e. the read from the App Configuration service) was not successful for any reason, then the latest cached copy of the configuration is used instead. The only time this fails is if there is no cached copy. We haven’t properly initialized the configuration yet. Adding in retry logic with incremental back-off would alleviate this problem as well unless the user had no network signal at all.
I don’t want to be doing LocalConfigurationStore.instance.kvStore.whatever() all the time, especially since the service is read-only, so I added common get() operations to the companion object to do that for me:
This allows me to do LocalConfigurationStore.get("auth.fb.appkey"), for example, to retrieve a specific key. The rest of my code is likely to know what keys they need.
Aside from the afore mentioned incremental back-off and retry logic, I might also distribute a config.json file in the res/raw directory. This can be read through resources.openRawResource(). The point? It allows me to distribute a “default” configuration and use that for the condition of “I’ve not downloaded the current configuration yet” situation.
I’m currently writing a cloud-native app with an Android (and iOS) front end. I’ve got my backend configuration written using Terraform, and it outputs a file called infrastructure.json that describes the backend in a JSON format. Now, the question becomes “how do I get that infrastructure.json file into my front end code?”