Career OS

Week 10 — CI/CD Pipeline + Basic Monitoring

The textbook for this week

The pipeline material lives in the CI/CD — GitHub Actions track: Anatomy for Days 1–2, Advanced when the basic pipeline works. Work the track; use this page as the weekly checklist.

Why This Week Matters

Deploying manually is amateur. Every real company has a pipeline: push code → tests run automatically → if tests pass → deploy automatically. You need to understand this and have it set up.

Daily Breakdown

Day 1-2: GitHub Actions CI Pipeline

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: splitwise_test
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: 'maven'
      
      - name: Run tests
        env:
          SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/splitwise_test
          SPRING_DATASOURCE_USERNAME: postgres
          SPRING_DATASOURCE_PASSWORD: postgres
        run: ./mvnw verify
      
      - name: Build Docker image
        run: docker build -t splitwise-api .

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      # Deploy step depends on your cloud provider
      # Railway auto-deploys from GitHub, so this may not be needed

What this does:

  1. Every push/PR → runs your tests against a real PostgreSQL (not mocks!)
  2. If tests pass → builds Docker image
  3. If on main branch → deploys

Day 3: Monitoring Basics

The three pillars of observability:

PillarWhatTool
LogsWhat happened (text records)Structured logging (SLF4J + Logback)
MetricsHow much/how fast (numbers)Micrometer + Prometheus
TracesRequest journey across servicesOpenTelemetry (later)

Add Prometheus metrics:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management.endpoints.web.exposure.include=health,info,prometheus

Now GET /actuator/prometheus exposes metrics like:

  • http_server_requests_seconds_count — how many requests
  • http_server_requests_seconds_sum — total time spent
  • jvm_memory_used_bytes — memory usage
  • hikaricp_connections_active — database connection pool status

Custom business metrics:

@Service
public class ExpenseService {
    private final Counter expenseCounter;
    
    public ExpenseService(MeterRegistry registry) {
        this.expenseCounter = Counter.builder("expenses.created")
            .tag("type", "split")
            .description("Number of expenses created")
            .register(registry);
    }
    
    public ExpenseDTO createExpense(...) {
        // ... business logic ...
        expenseCounter.increment();
        return dto;
    }
}

Day 4: Uptime Monitoring

Free uptime monitoring services:

  • UptimeRobot (free, 50 monitors) — pings your health endpoint every 5 min
  • Better Stack (free tier) — better UI, incident alerts

Set up UptimeRobot to monitor:

  • https://your-app.up.railway.app/actuator/health — every 5 minutes
  • Get email alerts when your app goes down

This gives you a number for your resume: “99.9% uptime over 30 days”

Day 5: API Documentation with Swagger

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version>
</dependency>

Visit http://localhost:8080/swagger-ui.html — auto-generated API docs.

Add annotations for clarity:

@Operation(summary = "Create a new expense in a group")
@ApiResponses({
    @ApiResponse(responseCode = "201", description = "Expense created"),
    @ApiResponse(responseCode = "400", description = "Invalid input"),
    @ApiResponse(responseCode = "404", description = "Group not found")
})
@PostMapping
public ResponseEntity<ApiResponse<ExpenseDTO>> createExpense(...) { }

Weekend: DSA + Review

  • CI pipeline running on every push
  • Swagger docs accessible on deployed URL
  • UptimeRobot monitoring your health endpoint
  • DSA: DSA 08 — Trees, BFS & DFS — module + 5-6 problems from its list

Resources

WhatWhereTime
GitHub Actionsdocs.github.com/actions quickstart30 min
Spring Boot + PrometheusBaeldung “Spring Boot Actuator + Prometheus”20 min
UptimeRobotuptimerobot.com10 min setup
SpringDoc OpenAPIspringdoc.org15 min