Kampung.SG

Kampung.SG
May-Aug 2023

ruby on rails
microservices
react
figma
agile
google cloud

Interfaith learning platform in collaboration with Google and Being Bridges. Designed and built with industry partners for the SUTD module 60.004 Service Design Studio.

Overview

Kampung.SG was a 12 week project delivered under Scrum methodology with a team of 7 students.

I was the Tech Lead and was responsible for DevOps and integration between the frontend and backend.

Our project site contains comprehensive documentation, including user journeys, service architecture, and design process.

Inspiration

Our team conducted user persona studies and interviews with our partner to understand the needs of their target users.

How might we make a platform to equip students with the prerequisite knowledge and an interactive learning community in a convenient and intuitive way?

Partners

From ideation to prototype, we worked with our industry partner Being Bridges to develop an AI-powered platform to equip Singapore students with interfaith knowledge and an interactive learning community.

Being Bridges is a Singapore-based social enterprise and consultancy with expertise developing practical community-building interventions around issues of diversity and social cohesion.

Mentors from Google also provided guidance on the technical aspects of the project.

Implementation

Configuration management

I was in charge of the microservices architecture and deployment. With six different services (API Gateway, User Service, Curriculum Service, Forum Service, ML Service, and Inactivity Checker), we needed to ensure consistent configuration while respecting each service's unique requirements.

Each service needed:

  • Service-specific URLs
  • Database credentials
  • API keys
  • Authentication details
  • Port configurations

Our solution was to implement a hierarchical approach to environment variables:

  • Default values coded in the application
  • Environment-specific values (.env.development, .env.production)
  • Secret values stored in Google Secret Manager for production

This allowed us to maintain flexibility and security across different environments.

Maintaining development environments

With developers using Windows, macOS, and Linux, creating a consistent development experience was challenging. Docker helped, but I ended up creating custom startup scripts for each OS:

# For macOS
osascript -e 'tell application "Terminal"
    activate
    tell application "System Events" to keystroke "t" using command down
    delay 1
    do script "cd '$PWD'/api-gateway" in selected tab of front window
    do script "bundle install --without production && rails s" in selected tab of front window
    # More services...
end tell'
REM For Windows
start "API Gateway" cmd.exe /k "cd api-gateway && rails s"
start "Curriculum" cmd.exe /k "cd curriculum-service && rails s"
REM More services...
# For Linux
tmux new-session -d -s kampung
tmux split-window -h
tmux select-pane -t 1
# Configure tmux panes for each service

Testing

Testing microservices properly required multiple levels of testing:

  • Unit tests within each service
  • Integration tests between directly connected services
  • End-to-end tests across the entire system

Cypress became our best friend for end-to-end testing, but setting it up to work with multiple services was challenging.

Deployment orchestration

Perhaps the biggest challenge was orchestrating deployments across multiple services. We needed to ensure that:

  • Services were deployed in the correct order
  • Backwards compatibility was maintained during the transition
  • Rollbacks were possible if issues were detected Database changes were applied at the right time

Our solution leveraged Google Cloud Build with service-specific triggers and dependencies. Each service had its own dedicated cloudbuild.yaml file.

steps:
  - id: "build image"
    name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}", "."]

  - id: "push image"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"]

  - id: "run deploy"
    name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
    entrypoint: gcloud
    args:
      [
        "run",
        "deploy",
        "${_SERVICE_NAME}",
        "--platform",
        "managed",
        "--region",
        "${_REGION}",
        "--image",
        "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}",
        "--port",
        "${_PORT}",
        "--allow-unauthenticated",
      ]