Wednesday, October 31, 2018

Feature Toggling Graphql Service with LaunchDarkly

For a project I was working on, we have a graphql service that retrieves clients positions from a SQL database. Under the hood there are some complex SQL stored procedures joining multiple SQL tables and complex data processing before they are finally in a usable shape. We want to move all this complexity to a Hive Big Data project which is more suitable to handle this type of data massaging and have our project consumed a Hive REST API.

The plan is to make sure that once the Hive REST API is live or even during UAT, the consumers of our graphql service do not notice any difference. And if something goes wrong with Hive, we can switch back to good old SQL with very minimal code change. This is where I think LaunchDarkly comes in handy as a fallback scenario. Fortunately I have some prior experience working with it.


The Toy Example

Now to illustrate what I'm dealing with, I have created a toy graphql service. Suppose we have a graphql query that retrieve clients positions defined as:

   type Query {
      positions: [String]
   }

Suppose the result of the query sourced from SQL is:

   {
     "data": {
       "positions": [
         "Sql1",
         "Sql2",
         "Sql3"
       ]
     }
   }

We want to be able to switch to Hive on the fly so that the same query returns:

   {
     "data": {
       "positions": [
         "Hive1",
         "Hive2",
         "Hive3"
       ]
     }
   }

Without feature toggling management, one way to achieve this is to introduce a boolean flag ie. use Hive vs use SQL. The graphql query definition becomes something like this:

   type Query {
      positions(useHive:Boolean!): [String]
   }

This solution isn't always possible because this means we have to change our public API. With LaunchDarkly there's no need to alter our public API, all the code needed for feature toggling happens internally.

Source Code

Using LaunchDarkly

If you're new to LaunchDarkly, see my other post for some background information how to get started.

Create a new feature flag and call it 'position-hive'. When this feature is switched ON, we'll make the positions query returning data from Hive. We can also target a specific set of users who are allowed to use this feature and another set who aren't.

Since I'm using node.js to build this graphql service, we will need LaunchDarkly SDK for Node.js. Install the Node.js SDK with npm:

    npm install ldclient-node --save

We want to make sure that LaunchDarkly client and user are initialised only once when the graphql service started. Once they are initialised, we passed them into the graphql context so we can retrieve these back when needed. Now in the real world there's usually user authentication and authorisation before we can use a service. I'm not going to cover that here, I'm just assuming that's already happening automagically and we can get the user id and name from the graphql request argument.

According to LaunchDarkly SDK documentation, the LaunchDarkly client will emit a 'ready' event when it has been initialized and can serve feature flags. waitForInitialization() function is an alternative to this 'ready' event. If your application uses promises to manage asynchronous operations, which is the case here, this is the recommended way to interact with the SDK. When LaunchDarkly client is ready and can serve feature flags then retrieving a flag setting for a given user can be done this way:

   ldClient.variation(FEATURE_KEY, ldUser, FEATURE_DEFAULT)
     .then(function(featureValue) {
         // application code
     });
The FEATURE_KEY is 'position-hive' and FEATURE_DEFAULT is either true or false. I set it to false in our case as I want our graphql service to use SQL data by default.

Source Code

https://github.com/velianarie/graphql-launchdarkly

Monday, October 29, 2018

Wi-Fi Problem Ubuntu 18.04 USB Boot on MacBook Pro

Always wanted to try Ubuntu but didn't want to risk changing anything in your machine? Well the community has thought about this. They termed it as live run, basically having the whole OS running from a CD, DVD, USB or any portable device you could think of, no installation needed. There are quite a few tutorials explaining how to do this, so I won't bother repeating it here. This tutorial is a good one to get it on any USB stick. I chose USB because that's what I had near me.

Booting from USB works like a charm, everything seems to work except the Wi-Fi! What can a machine do without internet these days? I looked into the Wi-Fi Settings and it's telling me it couldn't find a Wi-Fi adapter.

Unfortunately I don't have an extra machine to google for solution, so I had to use the good old Ethernet cable to connect to internet and that fortunately worked, I don't know what I would do if I had one of those thin laptops. Anyway if you google Ubuntu Wi-Fi problem, it will get you hundreds of hits... seems like this is a well-known problem and my research narrowed it down to missing driver.

To know what you're missing, the first thing you need to do is identify the network card you have. The command in Linux is lspci -vvnn | grep -A 9 Network.

   ubuntu@ubuntu:~$ lspci -vvnn | grep -A 9 Network
   02:00.0 Network controller [0280]: Broadcom Limited BCM4331 802.11a/b/g/n [14e4:4331] (rev 02)
      Subsystem: Apple Inc. AirPort Extreme [106b:00f5]
      ...
      Latency: 0, Cache Line Size: 256 bytes
      Interrupt: pin A routed to IRQ 17
      Region 0: Memory at a0600000 (64-bit, non-prefetchable) [size=16K]
      Capabilities: 
      Kernel driver in use: bcma-pci-bridge
      Kernel modules: bcma
This means I have:
  • The Chip ID: BCM4331
  • The PCI-ID: 14e4:4331
  • Kernel driver in use: bcma-pci-bridge

Fortunately I can see I don't have wl driver, this is easily remedied by installing bcmwl-kernel-source

   sudo apt install bcmwl-kernel-source
My Wi-Fi works afterwards.

If you're not as lucky as I am, knowing your network card is a first step to google for some more solution.

My MacBook Pro spec: macOS High Sierra, 2.9 Ghz Intel Core i7, 8 GB 1600 MHz DDR3