/wwsas2021

Worldwide Software Architecture Summit 2021 project

Primary LanguageTypeScriptMIT LicenseMIT

WorldWide Software Architecture Summit 2021

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

Development Environment

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

Infrastructure as Code

./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

Projects

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

Campaign API

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)
});

Product API

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))
}

User API

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)
    }));
  });
});

Web FrontEnd

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.