A Flutter app, a gRPC-server deployed in AKS, a grpc-gateway and more— tale of a fun 4-day hack!

Kaushik Chaubal
15 min readApr 25, 2020

TL;DR — Summary

If you are here looking for inspiration/help to write either a golang-based gRPC server (supporting all four kinds of service method), a proto file, a nodejs-based gRPC client, a golang-based gRPC client, a dart-based Flutter app, enhancing that flutter app as a gRPC client, creating a grpc-gateway to expose a RESTful JSON API, visualising your endpoints in a swagger-ui, dockerising your gRPC server, running the docker container on local Kubernetes cluster (using k8s dashboard as well as yaml configs), running the docker container on Azure Kubernetes Service… then you have two options:

Option 1: Go through my implementation and notes here: https://github.com/kaushikchaubal/home-recipes

Option 2: Carry on and read the rest of this article to understand how I implemented all this in 4 days — while learning from the community.

Introduction

What do you do when you have a staycation planned for 4-days? Well, in my case, I decided to hack around and build something using few tech toys that I really want to mess around with.

Getting ready to code!

Introducing… ‘Home Recipes’ — a project that helps to share, learn, innovative with recipes at home!

Disclaimer — the project’s domain is very trivial on purpose. You will also feel that the user experience is disconnected in the current implementation — this is also by design. The main aim of this project was/is to try different technologies and frameworks that I wanted to explore. So, instead of using the standard ‘to-do app’, I created a simple domain of my own.

The final architecture of home-recipes looks like this:

Architecture diagram

In the following few sections, I will be going through the details of how this was implemented. So, let’s get started, shall we?

Day 1: Building the server

It all started with with digital white-boarding — using miro. I decided to sketch down what I want to build, what tech-stack I want to use, the contract that I wanted to expose between server and clients, and the different screens that I planned to build. You can find my ‘home-recipes’ miro board here: https://miro.com/app/board/o9J_ktw-EKc=/

One of the key framework that I wanted to explore was gRPC — a language agnostic, high-performance Remote Procedure Call (RPC) framework built by Google. As explained here, The main benefits of gRPC are:

  • Modern, high-performance, lightweight RPC framework
  • Contract-first API development, using Protocol Buffers by default, allowing for language agnostic implementations
  • Tooling available for many languages to generate strongly-typed servers and clients
  • Supports client, server, and bi-directional streaming calls
  • Reduced network usage with Protobuf binary serialisation

Milestone 1: Defining the API

I defined my language-neutral, platform-neutral API using Protocol buffers. Here is a snippet of what that looked like:

service RecipesService {// Unaryrpc AddRecipe (AddRecipeRequest) returns (AddRecipeResponse);// Server-streamingrpc ListAllRecipes (ListAllRecipesRequest) returns (stream ListAllRecipesResponse);// Client-streamingrpc ListAllIngredientsAtHome (stream ListAllIngredientsAtHomeRequest) returns (ListAllIngredientsAtHomeResponse);// Bidirectional-streamingrpc GetIngredientsForAllRecipes (stream GetIngredientsForAllRecipesRequest) returns (stream GetIngredientsForAllRecipesResponse);}

One key detail in this API definition is that I was keen on trying out all four kinds of service method supported by gRPC, namely:

  1. Unary messages
  2. Server-side streaming
  3. Client-side streaming
  4. Bidirectional streaming

Milestone 2: Auto-generating the code-snippets

Google protocol buffer is compiled to generate language-specific code. It supports to multiple languages — Java, Python, Objective-C, and C++. And with the new proto3 language version, you can also work with Dart, Go, Ruby, and C#.

Run create the required stubs to be implemented in the server, I ran the following command:

protoc ./defs/recipes-service.proto --go_out=plugins=grpc:./server

(Full details available here: https://github.com/kaushikchaubal/home-recipes/blob/master/server/README.md)

Milestone 3: Creating a golang server

To implement the server, I decided to use golang. There are multiple reasons why golang was the language of choice to implement gRPC server but if you want more inspiration, check out this article: https://www.softkraft.co/why-choose-go-for-your-next-project/

The gRPC documentation has great write-ups on how to implement a server in golang. I followed it step-by-step and it worked like a charm. The key bit that I had to get my head around was how to ‘implement’ the APIs in the server. The following snippet of code highlights how the API contract is implemented:

func (s *server) AddRecipe(ctx context.Context, in *generated.AddRecipeRequest) (*generated.AddRecipeResponse, error) {
return &generated.AddRecipeResponse{Success: true}, nil
}

To get a good grasp about this, I highly recommend couple of guides:

  1. gRPC’s official ‘Getting started’ for golang: https://grpc.io/docs/quickstart/go/
  2. gRPC’s official ‘tutorial’ for golang: https://grpc.io/docs/tutorials/basic/go/

In case you are looking for home-recipes implementation for this, check out: https://github.com/kaushikchaubal/home-recipes/blob/master/server/recipes/main.go#L18

Milestone 4: Exposing different kinds of service method

gRPC lets you define four kinds of service method. Highly recommend reading the details for this here: https://grpc.io/docs/guides/concepts/

This is well visualised by jack (https://github.com/happy-python/) which I have copy-pasted here:

Source: https://github.com/happy-python/grpc-golang

In the case of home-recipes, this was all implemented here: https://github.com/kaushikchaubal/home-recipes/blob/master/server/recipes/main.go

Milestone 5: Creating test-clients in Node.js and Golang

In order to test this newly-build server, I need some test clients to ensure the communication is happening as expected. And so, I decided to create two different clients — first in Node.js and the second in Golang

I used gRPC’s official examples to be able to create the clients easily. The links to the examples that I used are as follows:

For home-recipes, all details steps of what I did to implement and run the test clients is documented as their respective README files.

… and with that, it was end of day 1!

Day 2: Building the client

The second day started with the realisation that I had NO idea about Flutter and how to build anything using this framework. Dart, the language used to implement in flutter, was also something I had never even run a hello-world in! The reason that I wanted to try out flutter was because i had heard multiple stories how people had selected Flutter over react-native and it has truly changed mobile app development. Additionally, I had heard that flutter was aggressively working to support new targets — web apps and desktop apps. There was only one way to find out if the hype was true or not — to build the client using flutter.

Milestone 6: Creating the first screen using Flutter

As you can imagine, doing mobile-app development in today’s world does require a hefty amount of setup. After toiling through and installing half of the internet, I was able to get a basic hello-world example running. There were couple of resources that I found very helpful as part of this exercise — the first is the official codelab available on flutter’s website: https://flutter.dev/docs/get-started/codelab. But additionally, I also breezed through the flutter course by app-brewery called ‘Introduction to Flutter Development’ (link: https://www.appbrewery.co/courses/enrolled/851555) which helped me get good clarity about some of the fundamentals of Dart as well as Flutter.

After toiling away for few hours, I was so proud to finally be able to have my first screen ready that looked like this:

First home-recipes screen built using Flutter — iOS and Android

Side note — While implementing this screen, there was a lot of focus to use material design (duh!) and great support for it in Flutter. One of the resources that I came across that I particularly found useful was materialpalette.com — it’s a helpful site that gives you different colour combinations and icons as recommended by the material design spec.

Milestone 7: Creating routes for different screens

As part of the implementation, I had realised that since I have four different endpoints that I aim to consume, I will most probably have to create four different screens to visualise the data in some way. And so, the next milestone that I started working on is to figure out how to have different screens connected to each other. The key resource to help out with this was: https://flutter.dev/docs/cookbook/navigation/navigation-basics, where the key bit is

onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
}

You can see the implementation of this in home-recipes here: https://github.com/kaushikchaubal/home-recipes/blob/master/client/home_recipes_app/lib/main.dart#L104

Milestone 8: Implementing the 4 screens

Now that the routes for the four screens were ready and functional, the next piece of the puzzle was to ensure that there are decent enough UI components to help visualise the interactions that I had defined earlier. One of the key ‘requirements’ that I had set for myself was to ensure that I keep using the material design UI components that flutter had. And so, I used the material components page as inspiration. Check out: https://flutter.dev/docs/development/ui/widgets/material and you will see how it has plenty of options grouped together to help implementation

Some of the key widgets that I used for the four screens include:

  • AppBar to support easy navigation
  • Basic form widgets like Cards, TextFields, FlatButtons
  • Datatable to capture data in a table
  • FilterChip (which is a cooler version of Checkboxes) to get multiple selections from the user
  • BottomNavigationBar — to see multiple ‘tabs’ on one screen

And so, as a result of these widgets, the UI started looking pretty neat. Here is a GIF that captures the screens and how the interaction looks like:

Working example of home-recipes (all screens)

… and with that, day 2 was over!

Day 3: Connecting the client and the server

At the start of day 3, I had a functioning server running locally and a functioning client running locally (on an emulator). But they were not connected to each other — and today’s big focus was making the client-server communication happen!

Milestone 9: Request-response from client to server

I came across a very helpful article which talked about a flutter client talking to a golang server which is powered by gRPC: https://medium.com/flutter-community/tutorial-asynchronous-flutter-chat-client-with-go-chat-server-which-are-powered-by-grpc-simple-ce913066861c. This is exactly what I was looking for and so, I started following the steps explained in that article in detail.

There were multiple things that I learnt while doing this milestone and yet, the most satisfying one was when I was able to create the stubs in dart using my proto definitions from milestone 1. The command that I used was:

protoc ./defs/recipes-service.proto  --dart_out=grpc:client/home_recipes_app/lib/generated

Full details of steps followed for home-recipes to make the client-server communicate is available here: https://github.com/kaushikchaubal/home-recipes/blob/master/client/home_recipes_app/README.md

Milestone 10: Support different streaming mechanisms

While the flutter app was now communicating successfully with the server, that was only for a simple request-response message type. As soon as I was trying to implement the other types (which all included different variations of streaming), I was lacking the required understanding required. That was until I came across this explanation of how to implement gRPC clients in dart (which I found extremely helpful!): https://github.com/grpc/grpc-dart/blob/master/example/route_guide/lib/src/client.dart.

Implementing a client to support gRPC streams without using Streams built within dart was not possible. And so, I had to get into the details of how async communication works in Dart. And I realised how powerful Dart is when I wrote this snippet of code:

await for (var response in stub.listAllRecipes(ListAllRecipesRequest())) {
streamController.add(response);
}

To be able to write that snippet of code, I had to understand some very fundamental things in Dart:

  1. Async programming in Dart: https://dart.dev/codelabs/async-await
  2. Creating and working with streams in Dart: https://dart.dev/articles/libraries/creating-streams

During the implementation, I did feel that I would have to use a state management library for Dart and https://bloclibrary.dev/#/ seemed to be the favoured on online. However, the current implementation that I have does not need bloc since I used the internally available streams to implement the screens.

All API interactions in home-recipes from the the client to the server are available here: https://github.com/kaushikchaubal/home-recipes/blob/master/client/home_recipes_app/lib/api/RecipesService.dart

Milestone 11: Updating UI elements based on streams

The last challenge that remained with this day was to take the data off the streams and to update the UI components as a result of it. Sounds simple and straightforward, no? Well, to be fair, most of the components were easy to update. But updating the DataTable was a milestone of it’s own! Why? For that, we need to see how the DataTable is implemented. Have a look at this for reference: https://www.tutorialkart.com/flutter/flutter-datatable/

As you can see from that reference, DataTable follows the following syntax:

DataTable(
columns: [
DataColumn(label: ),
DataColumn(label: ),
],
rows: [
DataRow(cells: [
DataCell( ),
DataCell( ),
]),
DataRow(cells: [
DataCell( ),
DataCell( ),
]),
]
)

And updating those DataRows without having access to the underlying model was very difficult. I also realised that I am not the only one that was trying to solve this problem. Multiple people had got stuck and tried things out as seen here: https://stackoverflow.com/questions/53997496/how-to-add-dynamically-items-to-the-table-in-flutter

And finally, after lots of trials, I got this working and the key implementation is in the initState method as seen here: https://github.com/kaushikchaubal/home-recipes/blob/master/client/home_recipes_app/lib/ShowAllRecipesRoute.dart#L20

As a result of which, I had all four screens successfully talking to the the golang server and getting & setting data as expected. It was beautiful…

… and with that, day 3 was done, but not without making me realise an important bug!

Day 4a: Building the middleware

Let’s start off with this bug that I was referring to… while testing the app on different targets, I had realised that the grpc package in Dart works as expected for iOS ad Android but for not for the web-app. And based on research, this is because you cannot make http2 connections from a web-page amd so, the recommendation is to maintain a RESTFul API system on the side to handle requests. Read the detailed description about this on this issue: https://github.com/flutter/flutter/issues/48054

So, as explained in the issue, one way of solving this is using grpc-gateway. To put simply, grpc-gateway reads a gRPC service definition, and generates a reverse-proxy server which translates a RESTful JSON API into gRPC. This server is generated according to custom options in your gRPC definition. Details of this can be found on their landing page: https://grpc-ecosystem.github.io/grpc-gateway/ but essentially, this diagram helped me to get good clarity of what value it adds

Source: https://grpc-ecosystem.github.io/grpc-gateway/

Milestone 12: Implementing grpc-gateway to expose a RESTful JSON API

Now that I knew the problem and what I need to do to implement it, I started researching the easiest way to get this up and running. I came across a very helpful write-up: https://grpc.io/blog/coreos/ and followed it on to be able to create the options for my proto file, create the grpc-gateway proto files and then, implement the gateway logic.

The key step needed to expose this RESTful APIs was to add the following options to the proto

rpc AddRecipe (AddRecipeRequest) returns (AddRecipeResponse) {
option (google.api.http) = {
post: "/v1/homerecipes/addRecipe"
body: "*"
};
}

Detailed steps followed for home-recipes middleware is available here: https://github.com/kaushikchaubal/home-recipes/blob/master/middleware/README.md. And soon, I had a gRPC endpoint up and running which I could communicate using POSTMAN.

Making RESTful calls to gRPC server via grpc-gateway

Milestone 13: Generating Swagger on top of gRPC

Swagger is an OpenAPI specification for describing RESTful services in a language-neutral and human-readable way. So we can generate a swagger file, then anyone who wants to interact with our service can understand how it works with little effort. It can also be used to generate documentation and when we update the service, we generate the swagger file again and our documentation updates with it.

I came across a very helpful article: https://levelup.gitconnected.com/grpc-basics-part-2-rest-and-swagger-53ec2417b3c4 which covered details of how to implement this on top of my grpc-gateway.

The key addition that I had to do for my proto file was to add the following option:

option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger)={ 
info: {
title: "home-recipes";
};
schemes: HTTP;
};

And so, at the end of this milestone, I had a auto-generated swagger.json file as you can see here: https://github.com/kaushikchaubal/home-recipes/blob/master/middleware/recipes-service.swagger.json as well as a full-fledged swagger UI to help users of my service navigate and understand all the endpoints that are available for consumption.

Day 4b: Running in Azure Kubernetes Service

Finally, to ensure that I am running my golang server in a cloud-native way, I wanted to make sure that dockerise my golang server and run it on kubernetes

Milestone 14: Dockerising the golang server

Running my golang server in docker had a big advantage — i didn’t have any dependencies on my machine and it could be easily shipped to run on any hardware of choice. And so, I followed the official guide to dockerise a golang server as found here: https://blog.golang.org/docker

You can find the home-recipes docker file here: https://github.com/kaushikchaubal/home-recipes/blob/master/server/Dockerfile which is a straightforward and simple Dockerfile. On creating the tag, and running it locally, I then pushed it to dockerhub as a public image so that I could use it (PS — feel free to use this image if you plan to do some development similar to what I have been doing).

This image can be found on dockerhub on https://hub.docker.com/repository/docker/kaushikchaubal/home-recipes (v0.0.1)

Milestone 15: Running on docker-desktop cluster

As I had my image ready, the next step was to run it on kubernetes — and to do that, I used my local kubernetes cluster to deploy it. I tried that in two ways:

  1. Using the Kubernetes dashboard
  2. Using a yaml file

Steps to start up the kubernetes dashboard locally can be found here: https://github.com/kaushikchaubal/home-recipes/blob/master/deploy/README.md (the secrets bit is probably the most important step to understand).

Once up, you can easily visualise, interact and edit your Deployment and Service from the dashboard UI — if you chose to. Here is a screenshot of what you should that look like:

Kubernetes dashboard running home-recipes Deployment

Also, to start up using a yaml meant that it was declarative and easy to reproduce on any cluster without human intervention. And so, there is a yaml file that I created which described the Deployment that I want to do (including the docker file to use), and the Service to have on top of it. You can get details of that in the YAML file: https://github.com/kaushikchaubal/home-recipes/blob/master/deploy/deploy-local.yaml

Milestone 16: Running on Azure Kubernetes Service

Finally, to ensure that this server is available over an external public IP, I wanted to run it on a cloud provider — in their managed Kubernetes cluster. As you are well aware, most cloud providers have managed Kubernetes cluster, and really, you can choose any cloud provider of choice. I chose Azure (and in case you didn’t know, you can create a free Azure account as explained here: https://azure.microsoft.com/en-gb/free/)

After creating the account and a resource group, you have to create the Kubernetes cluster. Detailed instructions that I found very helpful are available on this link: https://docs.microsoft.com/en-gb/azure/aks/kubernetes-walkthrough-portal

Once the cluster is up and running, all you have to do is to run

kubectl apply -f deploy/deploy-azure.yaml

And just like that, you have your gRPC server running on Azure Kubernetes Service. At the end of this milestone, your Azure cloud shell should be similar to what you see in this screenshot:

home-recipes running successfully on AKS (edited out sensitive data)

And after that, all you have to do is to make sure you are pointing your clients to connect to the external IP that your server is running and voila, it works!

… and with that, day 4 was a wrap!

Conclusion:

Hopefully, through this article, you were able to understand my experience with these technologies and get a glimpse of how I built the architecture described earlier during my 4-day hack.

Once of the big reasons for me to write all this down was to share more broadly all the resources that I found very helpful (but for which, I had to google a lot!).

If you have successfully reached this part of the article and if you think this article was helpful, please feel free to ‘clap’ for it and share it with your friends & colleagues (maybe even family!).

Also, as mentioned before, all my implementation of home-recipe is available here: https://github.com/kaushikchaubal/home-recipes — feel free to star it, fork it, create PRs to it!

--

--