昨天在 Application Layer Pt.1 中,我們專注於 CI/CD 流程 與 Web 應用部署,讓整個專案具備自動化佈署與前端呈現能力。
今天要進入 Application Layer 的第二部分 —— API 微服務與背景工作。
這一塊是整個系統的「業務核心」,所有訂單處理、支付流程、通知推送,甚至定期的批次任務,都是在這裡實作與運行的。換句話說,這是讓整個架構從「能運作」進一步變成「能提供價值」的關鍵。
Application Layer:
昨天我們已經完成了 Web 應用程式 與 CI/CD 的配置,今天則把重點放在 API 微服務 與 背景工作,讓整個應用層真正具備完整的業務能力。
加上昨天的內容整個專案結構會像這樣:
applications/
├── main.tf # 主要應用編排
├── variables.tf # 應用變數
├── outputs.tf # 應用輸出
├── web-app/ # 前端應用
│ ├── main.tf # Web 應用配置
│ ├── load-balancer.tf # 負載平衡器
│ ├── variables.tf
│ └── outputs.tf
├── cicd/ # CI/CD 管線
│ ├── artifact-registry.tf # 容器倉庫
│ ├── cloud-build.tf # 建置觸發器
│ ├── variables.tf
│ └── outputs.tf
├── api-services/ # API 微服務
│ ├── order-service.tf # 訂單服務
│ ├── payment-service.tf # 支付服務
│ ├── notification-service.tf# 通知服務
│ └── variables.tf
└── background-jobs/ # 背景工作
├── scheduler.tf # 排程任務
├── pubsub-worker.tf # 消費者 / 批次處理
└── variables.tf
# applications/api-services/order-service.tf
# 訂單服務
resource "google_cloud_run_service" "order_service" {
name = "${var.environment}-order-service"
location = var.region
template {
metadata {
labels = {
environment = var.environment
service = "order-service"
tier = "application"
}
annotations = {
"autoscaling.knative.dev/minScale" = var.microservice_config.min_instances
"autoscaling.knative.dev/maxScale" = var.microservice_config.max_instances
"run.googleapis.com/execution-environment" = "gen2"
"run.googleapis.com/vpc-access-connector" = data.terraform_remote_state.infrastructure.outputs.vpc_connector_name
"run.googleapis.com/vpc-access-egress" = "private-ranges-only"
}
}
spec {
container_concurrency = var.microservice_config.concurrency
timeout_seconds = var.microservice_config.timeout_seconds
service_account_name = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/order-service:latest"
resources {
limits = {
cpu = var.microservice_config.cpu_limit
memory = var.microservice_config.memory_limit
}
}
# 環境變數
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "SERVICE_NAME"
value = "order-service"
}
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.database_url
key = "latest"
}
}
}
env {
name = "REDIS_URL"
value_from {
secret_key_ref {
name = data.terraform_remote_state.platform.outputs.redis_connection_secret
key = "latest"
}
}
}
# Pub/Sub 設定
env {
name = "ORDER_EVENTS_TOPIC"
value = data.terraform_remote_state.platform.outputs.pubsub_topics.order_events
}
env {
name = "ORDER_PROCESSING_SUBSCRIPTION"
value = data.terraform_remote_state.platform.outputs.pubsub_subscriptions.order_processing
}
# 健康檢查
liveness_probe {
http_get {
path = "/health"
port = 8080
}
initial_delay_seconds = 30
timeout_seconds = 5
}
startup_probe {
http_get {
path = "/ready"
port = 8080
}
initial_delay_seconds = 15
timeout_seconds = 5
}
}
}
}
traffic {
percent = 100
latest_revision = true
}
lifecycle {
ignore_changes = [
template[0].spec[0].containers[0].image
]
}
}
# 後端服務 (用於負載平衡器)
resource "google_compute_backend_service" "order_service_backend" {
name = "${var.environment}-order-service-backend"
description = "Backend service for order service"
protocol = "HTTP"
port_name = "http"
timeout_sec = 30
backend {
group = google_compute_region_network_endpoint_group.order_service_neg.id
}
health_checks = [google_compute_health_check.order_service_health.id]
log_config {
enable = true
sample_rate = 0.1
}
}
resource "google_compute_region_network_endpoint_group" "order_service_neg" {
name = "${var.environment}-order-service-neg"
network_endpoint_type = "SERVERLESS"
region = var.region
cloud_run {
service = google_cloud_run_service.order_service.name
}
}
resource "google_compute_health_check" "order_service_health" {
name = "${var.environment}-order-service-health"
timeout_sec = 5
check_interval_sec = 30
healthy_threshold = 1
unhealthy_threshold = 3
http_health_check {
port = "80"
request_path = "/health"
}
}
# applications/api-services/payment-service.tf
# 支付服務
resource "google_cloud_run_service" "payment_service" {
name = "${var.environment}-payment-service"
location = var.region
template {
metadata {
labels = {
environment = var.environment
service = "payment-service"
tier = "application"
}
annotations = {
"autoscaling.knative.dev/minScale" = var.microservice_config.min_instances
"autoscaling.knative.dev/maxScale" = var.microservice_config.max_instances
"run.googleapis.com/execution-environment" = "gen2"
"run.googleapis.com/vpc-access-connector" = data.terraform_remote_state.infrastructure.outputs.vpc_connector_name
"run.googleapis.com/vpc-access-egress" = "private-ranges-only"
}
}
spec {
container_concurrency = var.microservice_config.concurrency
timeout_seconds = var.microservice_config.timeout_seconds
service_account_name = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/payment-service:latest"
resources {
limits = {
cpu = var.microservice_config.cpu_limit
memory = var.microservice_config.memory_limit
}
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "SERVICE_NAME"
value = "payment-service"
}
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.database_url
key = "latest"
}
}
}
# 支付 API 金鑰
env {
name = "PAYMENT_API_KEY"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.payment_api_key
key = "latest"
}
}
}
# Pub/Sub 設定
env {
name = "PAYMENT_EVENTS_TOPIC"
value = data.terraform_remote_state.platform.outputs.pubsub_topics.payment_events
}
env {
name = "PAYMENT_PROCESSING_SUBSCRIPTION"
value = data.terraform_remote_state.platform.outputs.pubsub_subscriptions.payment_processing
}
liveness_probe {
http_get {
path = "/health"
port = 8080
}
initial_delay_seconds = 30
timeout_seconds = 5
}
startup_probe {
http_get {
path = "/ready"
port = 8080
}
initial_delay_seconds = 15
timeout_seconds = 5
}
}
}
}
traffic {
percent = 100
latest_revision = true
}
lifecycle {
ignore_changes = [
template[0].spec[0].containers[0].image
]
}
}
# 後端服務
resource "google_compute_backend_service" "payment_service_backend" {
name = "${var.environment}-payment-service-backend"
description = "Backend service for payment service"
protocol = "HTTP"
port_name = "http"
timeout_sec = 30
backend {
group = google_compute_region_network_endpoint_group.payment_service_neg.id
}
health_checks = [google_compute_health_check.payment_service_health.id]
log_config {
enable = true
sample_rate = 0.1
}
}
resource "google_compute_region_network_endpoint_group" "payment_service_neg" {
name = "${var.environment}-payment-service-neg"
network_endpoint_type = "SERVERLESS"
region = var.region
cloud_run {
service = google_cloud_run_service.payment_service.name
}
}
resource "google_compute_health_check" "payment_service_health" {
name = "${var.environment}-payment-service-health"
timeout_sec = 5
check_interval_sec = 30
healthy_threshold = 1
unhealthy_threshold = 3
http_health_check {
port = "80"
request_path = "/health"
proxy_header = "NONE"
}
}
# IAM 設定 - 支付服務需要特殊權限
resource "google_cloud_run_service_iam_member" "payment_service_invoker" {
service = google_cloud_run_service.payment_service.name
location = google_cloud_run_service.payment_service.location
role = "roles/run.invoker"
member = "serviceAccount:${data.terraform_remote_state.infrastructure.outputs.app_service_account_email}"
}
# 支付服務的 IAM 綁定 (限制存取)
resource "google_cloud_run_service_iam_member" "payment_service_internal_access" {
service = google_cloud_run_service.payment_service.name
location = google_cloud_run_service.payment_service.location
role = "roles/run.invoker"
member = "serviceAccount:${google_service_account.payment_service_caller.email}"
}
# 專門用於呼叫支付服務的服務帳號
resource "google_service_account" "payment_service_caller" {
account_id = "${var.environment}-payment-caller-sa"
display_name = "Payment Service Caller Service Account"
description = "Service account for calling payment service"
}
# applications/api-services/notification-service.tf
# 通知服務
resource "google_cloud_run_service" "notification_service" {
name = "${var.environment}-notification-service"
location = var.region
template {
metadata {
labels = {
environment = var.environment
service = "notification-service"
tier = "application"
}
annotations = {
"autoscaling.knative.dev/minScale" = var.microservice_config.min_instances
"autoscaling.knative.dev/maxScale" = var.microservice_config.max_instances
"run.googleapis.com/execution-environment" = "gen2"
"run.googleapis.com/vpc-access-connector" = data.terraform_remote_state.infrastructure.outputs.vpc_connector_name
"run.googleapis.com/vpc-access-egress" = "private-ranges-only"
}
}
spec {
container_concurrency = var.microservice_config.concurrency
timeout_seconds = var.microservice_config.timeout_seconds
service_account_name = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/notification-service:latest"
resources {
limits = {
cpu = var.microservice_config.cpu_limit
memory = var.microservice_config.memory_limit
}
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "SERVICE_NAME"
value = "notification-service"
}
# 通知 Webhook 設定
env {
name = "NOTIFICATION_WEBHOOK"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.notification_webhook
key = "latest"
}
}
}
# Pub/Sub 設定
env {
name = "NOTIFICATION_EVENTS_TOPIC"
value = data.terraform_remote_state.platform.outputs.pubsub_topics.notification_events
}
env {
name = "NOTIFICATION_PROCESSING_SUBSCRIPTION"
value = data.terraform_remote_state.platform.outputs.pubsub_subscriptions.notification_processing
}
# Email 服務設定
env {
name = "SMTP_HOST"
value = "smtp.gmail.com"
}
env {
name = "SMTP_PORT"
value = "587"
}
liveness_probe {
http_get {
path = "/health"
port = 8080
}
initial_delay_seconds = 30
timeout_seconds = 5
}
startup_probe {
http_get {
path = "/ready"
port = 8080
}
initial_delay_seconds = 15
timeout_seconds = 5
}
}
}
}
traffic {
percent = 100
latest_revision = true
}
lifecycle {
ignore_changes = [
template[0].spec[0].containers[0].image
]
}
}
# 後端服務
resource "google_compute_backend_service" "notification_service_backend" {
name = "${var.environment}-notification-service-backend"
description = "Backend service for notification service"
protocol = "HTTP"
port_name = "http"
timeout_sec = 30
backend {
group = google_compute_region_network_endpoint_group.notification_service_neg.id
}
health_checks = [google_compute_health_check.notification_service_health.id]
log_config {
enable = true
sample_rate = 0.1
}
}
resource "google_compute_region_network_endpoint_group" "notification_service_neg" {
name = "${var.environment}-notification-service-neg"
network_endpoint_type = "SERVERLESS"
region = var.region
cloud_run {
service = google_cloud_run_service.notification_service.name
}
}
resource "google_compute_health_check" "notification_service_health" {
name = "${var.environment}-notification-service-health"
timeout_sec = 5
check_interval_sec = 30
healthy_threshold = 1
unhealthy_threshold = 3
http_health_check {
port = "80"
request_path = "/health"
}
}
# applications/background-jobs/report-generator.tf
# 報表生成 Cloud Run Job
resource "google_cloud_run_v2_job" "report_generator" {
name = "${var.environment}-report-generator"
location = var.region
template {
labels = {
environment = var.environment
service = "report-generator"
tier = "application"
type = "batch-job"
}
template {
service_account = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
# 任務設定
task_count = 1
parallelism = 1
# 任務超時 (報表生成可能需要較長時間)
task_timeout = "3600s"
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/report-generator:latest"
resources {
limits = {
cpu = var.background_job_config.cpu_limit
memory = var.background_job_config.memory_limit
}
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "JOB_TYPE"
value = "report-generator"
}
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.database_url
key = "latest"
}
}
}
# 報表輸出目標
env {
name = "REPORTS_BUCKET"
value = data.terraform_remote_state.platform.outputs.storage_bucket_names.backups
}
# 執行參數
env {
name = "REPORT_TYPE"
value = "daily"
}
# 報表格式
env {
name = "OUTPUT_FORMAT"
value = "pdf,csv"
}
# 重試設定
env {
name = "MAX_RETRIES"
value = "3"
}
# 通知設定
env {
name = "NOTIFICATION_TOPIC"
value = data.terraform_remote_state.platform.outputs.pubsub_topics.notification_events
}
}
}
}
lifecycle {
ignore_changes = [
template[0].template[0].containers[0].image
]
}
}
# Cloud Scheduler 觸發器 (每日執行)
resource "google_cloud_scheduler_job" "daily_report" {
name = "${var.environment}-daily-report"
description = "Daily report generation"
schedule = "0 6 * * *" # 每日早上 6 點
time_zone = "Asia/Taipei"
attempt_deadline = "300s"
retry_config {
retry_count = 3
}
http_target {
http_method = "POST"
uri = "<https://$>{var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}/jobs/${google_cloud_run_v2_job.report_generator.name}:run"
oauth_token {
service_account_email = data.terraform_remote_state.infrastructure.outputs.build_service_account_email
}
body = base64encode(jsonencode({
overrides = {
containerOverrides = [{
name = "report-generator"
env = [{
name = "REPORT_DATE"
value = "yesterday"
}]
}]
}
}))
}
}
# 週報生成
resource "google_cloud_scheduler_job" "weekly_report" {
name = "${var.environment}-weekly-report"
description = "Weekly report generation"
schedule = "0 7 * * 1"
time_zone = "Asia/Taipei"
attempt_deadline = "300s"
retry_config {
retry_count = 2
}
http_target {
http_method = "POST"
uri = "<https://$>{var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}/jobs/${google_cloud_run_v2_job.report_generator.name}:run"
oauth_token {
service_account_email = data.terraform_remote_state.infrastructure.outputs.build_service_account_email
}
body = base64encode(jsonencode({
overrides = {
containerOverrides = [{
name = "report-generator"
env = [
{
name = "REPORT_TYPE"
value = "weekly"
},
{
name = "REPORT_DATE"
value = "last_week"
}
]
}]
}
}))
}
}
# applications/background-jobs/data-sync.tf
# 資料同步 Cloud Run Job
resource "google_cloud_run_v2_job" "data_sync" {
name = "${var.environment}-data-sync"
location = var.region
template {
labels = {
environment = var.environment
service = "data-sync"
tier = "application"
type = "batch-job"
}
template {
service_account = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
task_count = 1
parallelism = 1
task_timeout = "1800s"
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/data-sync:latest"
resources {
limits = {
cpu = var.background_job_config.cpu_limit
memory = var.background_job_config.memory_limit
}
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "JOB_TYPE"
value = "data-sync"
}
env {
name = "SOURCE_DATABASE_URL"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.database_url
key = "latest"
}
}
}
# 目標資料倉儲 (BigQuery)
env {
name = "TARGET_DATASET"
value = "${var.project_id}.analytics"
}
# 同步批次大小
env {
name = "BATCH_SIZE"
value = "1000"
}
# 同步策略
env {
name = "SYNC_MODE"
value = "incremental"
}
# 資料表列表
env {
name = "SYNC_TABLES"
value = "orders,payments,users,products"
}
# 錯誤處理
env {
name = "ERROR_THRESHOLD"
value = "100"
}
# 通知設定
env {
name = "NOTIFICATION_TOPIC"
value = data.terraform_remote_state.platform.outputs.pubsub_topics.notification_events
}
}
}
}
lifecycle {
ignore_changes = [
template[0].template[0].containers[0].image
]
}
}
# Cloud Scheduler 觸發器 (每小時執行)
resource "google_cloud_scheduler_job" "hourly_data_sync" {
name = "${var.environment}-hourly-data-sync"
description = "Hourly data synchronization"
schedule = "0 * * * *" # 每小時
time_zone = "Asia/Taipei"
attempt_deadline = "300s"
retry_config {
retry_count = 2
}
http_target {
http_method = "POST"
uri = "<https://$>{var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}/jobs/${google_cloud_run_v2_job.data_sync.name}:run"
oauth_token {
service_account_email = data.terraform_remote_state.infrastructure.outputs.build_service_account_email
}
}
}
# 每日完整同步 (凌晨執行)
resource "google_cloud_scheduler_job" "daily_full_sync" {
name = "${var.environment}-daily-full-sync"
description = "Daily full data synchronization"
schedule = "0 1 * * *"
time_zone = "Asia/Taipei"
attempt_deadline = "600s"
retry_config {
retry_count = 1
}
http_target {
http_method = "POST"
uri = "<https://$>{var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}/jobs/${google_cloud_run_v2_job.data_sync.name}:run"
oauth_token {
service_account_email = data.terraform_remote_state.infrastructure.outputs.build_service_account_email
}
body = base64encode(jsonencode({
overrides = {
containerOverrides = [{
name = "data-sync"
env = [
{
name = "SYNC_MODE"
value = "full"
},
{
name = "BATCH_SIZE"
value = "5000"
}
]
}]
}
}))
}
}
# applications/background-jobs/cleanup-jobs.tf
# 資料庫清理 Job
resource "google_cloud_run_v2_job" "db_cleanup" {
name = "${var.environment}-db-cleanup"
location = var.region
template {
labels = {
environment = var.environment
service = "db-cleanup"
tier = "application"
type = "maintenance-job"
}
template {
service_account = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
task_count = 1
parallelism = 1
task_timeout = "1800s"
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/db-cleanup:latest"
resources {
limits = {
cpu = var.background_job_config.cpu_limit
memory = var.background_job_config.memory_limit
}
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "JOB_TYPE"
value = "db-cleanup"
}
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = data.terraform_remote_state.infrastructure.outputs.secret_names.database_url
key = "latest"
}
}
}
# 清理策略
env {
name = "CLEANUP_DAYS"
value = var.environment == "production" ? "90" : "30"
}
env {
name = "DRY_RUN"
value = var.environment == "production" ? "false" : "true"
}
# 清理目標
env {
name = "CLEANUP_TABLES"
value = "audit_logs,session_data,temp_files"
}
# 安全限制
env {
name = "MAX_DELETE_ROWS"
value = "10000"
}
# 通知設定
env {
name = "NOTIFICATION_TOPIC"
value = data.terraform_remote_state.platform.outputs.pubsub_topics.notification_events
}
}
}
}
lifecycle {
ignore_changes = [
template[0].template[0].containers[0].image
]
}
}
# Cloud Scheduler 觸發器 (每週執行)
resource "google_cloud_scheduler_job" "weekly_cleanup" {
name = "${var.environment}-weekly-cleanup"
description = "Weekly database cleanup"
schedule = "0 2 * * 0" # 每週日凌晨 2 點
time_zone = "Asia/Taipei"
attempt_deadline = "300s"
retry_config {
retry_count = 1
}
http_target {
http_method = "POST"
uri = "<https://$>{var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}/jobs/${google_cloud_run_v2_job.db_cleanup.name}:run"
oauth_token {
service_account_email = data.terraform_remote_state.infrastructure.outputs.build_service_account_email
}
}
}
# 日誌清理 Job
resource "google_cloud_run_v2_job" "log_cleanup" {
name = "${var.environment}-log-cleanup"
location = var.region
template {
labels = {
environment = var.environment
service = "log-cleanup"
tier = "application"
type = "maintenance-job"
}
template {
service_account = data.terraform_remote_state.infrastructure.outputs.app_service_account_email
task_count = 1
parallelism = 1
task_timeout = "900s" # 15 分鐘
containers {
image = "${data.terraform_remote_state.applications.outputs.docker_repository_url}/log-cleanup:latest"
resources {
limits = {
cpu = "0.5"
memory = "512Mi"
}
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "LOG_BUCKET"
value = data.terraform_remote_state.platform.outputs.log_bucket_name
}
# 保留期限
env {
name = "RETENTION_DAYS"
value = var.environment == "production" ? "365" : "90"
}
}
}
}
lifecycle {
ignore_changes = [
template[0].template[0].containers[0].image
]
}
}
# 每月日誌清理
resource "google_cloud_scheduler_job" "monthly_log_cleanup" {
name = "${var.environment}-monthly-log-cleanup"
description = "Monthly log cleanup"
schedule = "0 3 1 * *" # 每月 1 號凌晨 3 點
time_zone = "Asia/Taipei"
attempt_deadline = "300s"
retry_config {
retry_count = 1
}
http_target {
http_method = "POST"
uri = "<https://$>{var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}/jobs/${google_cloud_run_v2_job.log_cleanup.name}:run"
oauth_token {
service_account_email = data.terraform_remote_state.infrastructure.outputs.build_service_account_email
}
}
}
呼~隨著 Application Layer 的收尾,這三天的實作終於完成,三層架構也真正成形。從最基礎的 Infrastructure Layer 到支援共用能力的 Platform Layer,再到承載業務邏輯的 Application Layer,每一層都有它的角色與責任,整個系統逐步搭建起來後,真的有種「完整架構落地」的感覺。
這次是我第一次完整練習從零建立三層架構~過程中體會到:
總之,這三天不只是完成了技術上的部署,更是一次從概念到實作的完整體驗。未來在設計大型專案或維護現有系統時,這些經驗一定會很有幫助。