WorldWide Software Architecture Summit 2021 event dates are August 3rd, 2021 and August 4th, 2021
As a speaker in the event, I prepared this repo as a guideline to build a solution with following requirements;
- Built with microservices approach in mind
- Built with several components, such as, a React frontend, a .Net 5 backend api, a Go backend api, a NodeJs backend api
- All the components must be containerized with Docker
- All components must live inside of a Kubernetes cluster
- Easy scalability using Kubernetes deployments
- Published to Azure
- No vendor lock, must be publishable to other cloud providers
- Fully automated using GitHub Actions
I'd like to see you in my session (Building and Automating a solution with Microservices approaches using .Net, Go, Node, Kubernetes and GitHub Actions), so we can discuss the solution even further.
Index
For the project, we're using DevContainers to define the development environment. GitHub Codespaces are using DevContainers under-the-hood, so, one stone, two birds. We'll have both local and remote environments super easily, with the help of DevContainers.
You can find more detailed explanation of DevContainers here and here
Let's start building the development environment
-
Create devcontainer.json file under .devcontainer folder at the root of the project
We can define;
name of the development environment
"name": "Development Environment",
settings that are gonna applied to Visual Studio Code
"settings": { "terminal.integrated.profiles.linux": { "bash": { "path": "/bin/bash" } }, "workbench.iconTheme": "vscode-icons" },
extensions that are gonna installed to Visual Studio
"extensions": [ "ms-dotnettools.csharp", "golang.go", "ms-vscode.vscode-node-azure-pack", "durablefunctionsmonitor.durablefunctionsmonitor", "ms-azuretools.vscode-docker", "editorconfig.editorconfig", "vscode-icons-team.vscode-icons", "humao.rest-client" ]
forwarded ports from inside of the DevContainer to host machine
"forwardPorts": [ 5000 ],
bash commands that is gonna run after DevContainer created
PS: In this example, we're starting login process of the GitHub CLI tool. So we ensure that the development environment has the GitHub CLI logged in after it's created
"postCreateCommand": "gh auth login --web",
-
Create Dockerfile under the .devcontainer folder
We can start with the image of the framework we used in one of the main projects, such as, GoLang Docker Image, Node Docker Image, .Net SDK Docker Image
In this solution, we're gonna start from GoLang Docker Image
FROM golang:1.16.5-buster
PS: At the time of writing this guideline, latest go version is 1.16.5, there may be newer versions when you read this
In this Dockerfile, we're gonna install and configure all the tools, libraries, sdks that we'll use through-out the development, such as;
-
Create .editorconfig file at the root of the solution
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
- Create .gitignore file at the root of the solution and ignore unneeded files from the git system, for example,
bin/
obj/
Properties/
node_modules/
dist/
package-lock.json
bundle.css
./iac/setup.sh script file includes all the Bash code to do below checks, provision below resources on Azure and complete the setup by executing below;
- Check if Azure CLI is exists, if not, install Azure CLI
- Check if GitHub CLI is exists, if not, install GitHub CLI and proceed to login
- If Azure CLI is not loggedin yet, proceed to login to Azure
- Create a Resource Group on Azure
- Create an Azure Container Registry (ACR), to hold project specific Docker Images
- Create an Azure Kubernetes Services (AKS) linked to the Azure Container Registry (ACR), to have a compute power to run project Docker Images
- Reset kubectl config and set current context to Azure Kubernetes Services (AKS) instance
- Create new Service Principal and set it as secret to the GitHub CLI
- Install nginx into the Azure Kubernetes Services (AKS) instance as Ingress Controller to handle incoming traffic
- Apply ingress.yml config onto the Ingress Controller to associate endpoints to services
- path: /api/product/(.*)
pathType: Prefix
backend:
service:
name: product-service
port:
number: 80
- path: /api/campaign/(.*)
pathType: Prefix
backend:
service:
name: campaign-service
port:
number: 80
- path: /api/user/(.*)
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80
- path: /(.*)
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
There are 4 projects in the solution;
- api-campaing: NodeJs project that handles /api/campaign/* requests
- api-product: GoLang project that handles /api/product/* requests
- api-user: .Net 5 project that handles /api/user/* requests
- web-frontend: React project that renders frontend
Execute following command to create the project;
npm init --force && npm i express && touch server.js
Add following script into the package.json file;
"scripts": {
"start": "node server.js"
}
Open server.js and add following javascript code;
const app = require('express')();
app.get('/get-current', function (req, res) {
res.send(JSON.stringify({
title: "Great Campaign",
pictureUrl: "https://picsum.photos/1920/100",
gotoUrl: "https://devopstips.net"
}));
});
const server = app.listen(7000, function () {
const host = server.address().address
const port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
});
Execute following command to create the project;
go mod init github.com/polatengin/wwsas2021 && touch main.go
Open main.go and add following golang code;
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Product struct {
Name string
Price float32
InStock bool
Rating int
ImageUrl string
}
type ProductList []Product
func main() {
http.HandleFunc("/get-list", func(w http.ResponseWriter, r *http.Request) {
productList := ProductList{ /* Removed for the sake of brevity */ }
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(productList)
})
log.Fatal(http.ListenAndServe(":9000", nil))
}
Execute following command to create the project;
dotnet new web
Open Startup.cs and add following CSharp code;
app.UseEndpoints(endpoints =>
{
endpoints.MapPost("/login", async context =>
{
await JsonSerializer.SerializeAsync(context.Response.Body, new
{
BirthDate = DateTime.Now.AddDays(random.Next(-10000, -1000)),
Salary = ((random.NextDouble() * 100000) + 50000).ToString("0.##"),
Name = words[random.Next(words.Length)] + " " + words[random.Next(words.Length)],
ProfilePictureUrl = "https://i.pravatar.cc/50?" + random.Next(1, 1000)
});
});
endpoints.MapPost("/get-user-list", async context =>
{
await JsonSerializer.SerializeAsync(context.Response.Body, Enumerable.Range(1, random.Next(5, 15)).Select(index => new
{
BirthDate = DateTime.Now.AddDays(index),
Salary = ((random.NextDouble() * 100000) + 50000).ToString("0.##"),
Name = words[random.Next(words.Length)] + " " + words[random.Next(words.Length)],
ProfilePictureUrl = "https://i.pravatar.cc/50?" + random.Next(1, 1000)
}));
});
});
Execute following command to create the project;
npx create-react-app --template typescript . && npm i autoprefixer tailwindcss
Open App.tsx file and modify the content with the codes from this repo.