I recently wrote a post on configuration management (Golang Configuration in 12 Factor Applications). It got me thinking about writing a series of posts about what is meant when we talk about a 'cloud native' application. So this is the beginning of a series on writing, from scratch, a cloud-native application.
I am choosing a simple application so that the focus is more on the principles of development.
The project will be an API that allows one to find out information about SuperHeroes. I pulled the data down from Kaggle.
Using this project I will walk through how to declare your dependencies in a reliable manner, how to setup and use a CI/CD pipeline (using Gitlab and AWS lambda). I touch briefly on how to handle state in a distributed environment. I will walk through how one starts to build a project, highlight flaws in the architecture and supporting infrastructure and pull them out until we have a well-defined service that is capable of running at high-scale.
I will also comment on how relevant the original manifesto's agendas are in today's environment 7 years on. Note that I will not be going through each of the Factors in the same order as defined in the manifesto as it does not allow me to incrementally build upon the project.
And with that, let’s dive in!
Codebase
Fundamentally the rule is to keep each codebase in a separate repository. This has a few advantages which become more obvious when applied to microservices. We want to have well-defined boundaries between services. This reduces large and non-obvious dependencies which can harm horizontal scalability (an essential part of cloud-native). This ties in with Backing Services (coming up) of treating all dependencies as 3rd-party service. A single codebase per service allows for much more fine-grained control of each service, such as releasing security-patches without having to notify or synchronise with dependent (or depending) teams and services.
Not everyone agrees with this, in fact Google uses a huge Monorepo although it is heavily customised and optimised towards their workflow (see https://dl.acm.org/citation.cfm?id=2854146 for a paper on the subject). It is easier to get started with Git as it is considerably better supported than any other tool. The idea behind a single codebase is to enforce well-defined contracts between different core processes within your project.
Below I quickly walk you through setting up the project and deploying an initial version of the project in preparation for the next post.
Practical
I am putting the relevant csv files into the Repo and generating a sample project ready to be used with AWS lambda. You can find the initial version on Gitlab: https://gitlab.com/ContainerSolutions/superhero-query/tags/codebase.
I have, for simplicity, decided to use Chalice to build the project. This should significantly ease the process of interaction with AWS. To get us started, we can install chalice and initialise a new repository:
pip install chalice
chalice new-project superhero-query
We then verify that we can deploy our project using:
chalice deploy
We first need to configure the AWS cli. The official documentation is pretty decent on how to do that https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html.
Now we can navigate to the supplied url and view our deployed code:
curl https://rnbn7cr4lb.execute-api.eu-west-1.amazonaws.com/api/
{"hello": "world"}
We have a working project! Let's now start the process of preparing for cloud-native.
I have already added a few dependencies to the project, and will start going through the code and begin the cloud-native transformation.
Join me next time as I jump into Dependency management and Isolation with Docker and Python's pip.
Read more about our work in The Cloud Native Attitude.