This week at the office we started work on a big enterprise project. We decided to go with Microservices architecture for the following reasons:
- We are working on a business domain composed of multiple subdomains
- The application needs to scale to twice the current load. They already have an application built using legacy technologies. We have to modernize the technology stack along with scaling to twice the current load.
- Since this is a big project we will have multiple teams working on it. Microservices architecture is well suited for a large and growing engineering team.
- Help the organization become API first. All the business APIs will be exposed using an enterprise gateway that third parties can integrate with.
In my experience working on Microservices architecture with a large and varied experience team it is always a struggle to enforce cross cutting concerns uniformly in all the Microservices. There are always some surprises and those are detected late in the project lifecycle when they cost more. The concerns that I am talking about are:
- Logging
- Health checks
- Performance monitoring
- Externalized configuration
- Preconfigured linting and static analysis
- Uniformity in package structure
- Open API documentation
- Gzip compression
- Documentation templates
- Observability via actuator
- Many more..
Earlier I solved this problem by creating a starter template Git repository and asking developers to use it as base. The base template is created using Spring Initializr in case of the Spring Boot. If all developers use Spring Initialzr to generate Microservice boilerplate then all of them will end up with different dependencies/versions. The custom base template Git repository works but it can’t do smart things with it. The smarts that I am looking for are:
- Ability to provide a base package
- Setting random port for the Microservice
- Using custom name for classes
- Templatizing all the names so that it matches the Microservices domain objects
- Faster getting started experience for the developer
- Uniform naming conventions. We follow a convention to use kebab-case for naming Microservices
As a side note I found out that there are 11 formats for Multiple-word identifier.
In essence we want developers to run a command, override some values and get a freshly baked Microservices boilerplate that they can start using. I have seen development teams waste a day or two each time they create a new Microservice.
I decided to use a Python command-line utility called cookiecutter that makes it possible to create customizable boilerplates. You can use it to create boilerplates for any language.
The way cookiecutter works is that you first create a cookiecutter template with its configuration and then use that template using cookiecutter CLI (or your own Python CLI that wraps cookiecutter API) to create your Microservices boilerplate.
Let’s create a directory for the cookiecutter Microservice template and change directory to it.
$ mkdir cookiecutter-spring-boot-ms-template
You will need Python 3 to install cookiecutter.
On mac, you can use brew to install Python3
$ brew install python3
Once installed, we can install cookiecutter
$ python3 -m pip install cookiecutter
Since we need to create Spring Boot Microservice boilerplate we will go to https://start.spring.io/ and download the boilerplate by specifying the following values and dependencies.
Download the zip in the cookiecutter-spring-boot-ms-template, unzip it, and remove the zip file.
Now, we will have a demo folder with our Spring Boot boilerplate inside the cookiecutter-spring-boot-ms-template directory.
As I discussed earlier we want to follow the kebab-case naming convention for our Microservices. For example, if someone specifies that they have to create Todo Microservice we should create microservice with name `todo-service`
To achieve that we will rename the `demo` folder to `cookiecutter.service_slug`.
cookiecutter-spring-boot-ms-template >> ls
{{ cookiecutter.service_slug }}
cookiecutter makes use of a file called `cookiecutter.json` where you have to declare variables that you will use. In our case we will ask the user to specify the name of the service like `My Service` and we will create a slug like `my-service` from it.
The cookiecutter.json file is shown below.
{
"service_name": "My Service",
"service_slug": "{{ cookiecutter.service_name.lower()|replace('-', ' ')|replace(' ', '-')|replace('.', '-')|trim() }}"
}
As you can see above we have defined the default value for `service_name`. The `service_slug` is created from the `servcie_name`. Users will have the flexibility to change it if required.
To see this in action let’s go to another directory where we want to create our Microservice boilerplate from cookiecutter template.
cd /tmp
Now, we will use cookiecutter CLI to create the Microservice boilerplate. We will use `service_name` as `Todo Service` and `service_slug`will be`todo-service`.
$ cookiecutter ~/dev/cookiecutter-spring-boot-ms-template
service_name [My Service]: Todo Service
service_slug [todo-service]:
The above generates `todo-service` in the current directory. You can view that using ls command.
$ ls
todo-service
Let’s extend this to rename `DemoApplication.java` to `TodoServcieApplication.java`. We can rename both the filename and class name to `{{ cookiecutter.service_name.title().replace(‘ ‘,”) | trim()}}`. The expression converts service_name to title case, then removes space in the name and finally trim it.
Run the cookiecutter again, this time our main class will be generated with the name `TodoServiceApplication`.
Similarly, we can also rename the package structure by using `{{ cookiecutter.service_slug.replace(‘-‘,”) }}` template.
One thing that is not possible while directly using cookiecutter CLI is to generate random numbers. The use case that I have is to assign a random port between 10000-15000 instead of the default port of 8080.
We can achieve that by writing our own CLI by wrapping cookiecutter API as shown below.
#!/usr/bin/env python3
from cookiecutter.main import cookiecutter
import random
service_port = random.randint(10000,15000)
template_dir = '../cookiecutter-spring-boot-ms-template'
output_dir = '/tmp'
cookiecutter(
template_dir,
output_dir=output_dir,
extra_context={'service_port': service_port}
)
You will have to make it executable by running the following command.
$ chmod +x msgen.py
Also, make sure to add following to cookiecutter template application.properties file
server.port={{ cookiecutter.service_port }}
You can run the `./msgen.py` and it will create the Microservices boilerplate in the `/tmp` directory.
All the code is available On Github.
If people are interested I will also share our production boilerplate which has many other goodies.
Hi Shekhar,
It will be really helpful if you can share the production version as well.
Hi Shekhar!
Thanks for spending time to create this, very helpful quick tutorial! It definitely saved lot of time of me going into Cookiecutter documentation and trying to figure out what I can use and can’t. Great work!
I would really appreciate if you could please share your production boilerplate version with me.
Thanks,
Gauzy
Nice article which help developers and teams spend less time on bolder plate cross cutting concerns and ability to deliver services faster.
Can you please share production boilerplate version.