はじめに
スタートアップでサービスを立ち上げる際、初期コスト削減と将来的なスケーラビリティの両立は常に課題です。本記事では、開発環境ではマイクロサービス、本番環境ではモノリシックで運用し、収益化に応じて瞬時にマイクロサービスに切り替え可能なアーキテクチャをご紹介します。
🎯 設計思想とメリット
なぜこのアーキテクチャを選んだのか
-
初期コスト削減
- クラウド環境で20個以上のpodを動かすとコストが高い
- 小規模サービスではマイクロサービスのメリットが薄い
-
データ不整合リスク回避
- モノリシック→マイクロサービス移行時の分散データベース問題
- 事前にサービス境界を明確にしておくことで移行リスクを最小化
-
開発効率とパフォーマンスの両立
- 開発時はマイクロサービスで責任分離とテスト効率を向上
- 本番時はモノリシックで通信オーバーヘッドを削減
🏗️ アーキテクチャ概要
📁 OmniDesk/
├── 📁 services/
│ ├── 📁 api/
│ │ ├── 📁 development/ # マイクロサービス用
│ │ │ ├── 📁 account/
│ │ │ │ ├── 📁 user/
│ │ │ │ ├── 📁 plan/
│ │ │ │ └── 📁 payment/
│ │ │ ├── 📁 inbox/
│ │ │ └── 📁 input/
│ │ └── 📁 product/ # モノリシック用
│ │ ├── 📁 controllers/
│ │ ├── 📁 models/
│ │ ├── 📁 routes/
│ │ └── 📁 migrations/
│ ├── 📁 ui/ # React + TypeScript
│ ├── 📁 integration/ # 外部連携
│ └── 📁 batch/ # バッチ処理
├── 📁 k8s/ # Kubernetes設定
├── 📁 compose/ # Docker Compose設定
├── 📁 infra/ # インフラ構成
└── 📁 scripts/reflect/ # 自動変換スクリプト
🔧 マイクロサービス構成(開発環境)
1. サービス分割戦略
各サービスは独立したDockerコンテナとして動作し、明確な責任境界を持ちます。
アカウント系サービス例
// services/api/development/account/user/routes/routes.go
package routes
import (
"github.com/gin-gonic/gin"
"OmniDesk-user-backend/controllers"
middleware "promochain.com/external/module/middleware"
)
func SetupRoutes(router *gin.Engine, db *gorm.DB, rabbitManager *middleware.RabbitMQManager) {
userRoutes := router.Group("/api/users")
{
userRoutes.GET("/:id", controllers.GetUser)
userRoutes.PUT("/:id", controllers.UpdateUser)
userRoutes.POST("/register", controllers.RegisterUser)
userRoutes.POST("/login", controllers.LoginUser)
userRoutes.PUT("/update-language", controllers.UpdateLanguageCode)
}
// RabbitMQ設定
amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%s/",
os.Getenv("RABBIT_MQ_USER"),
os.Getenv("RABBIT_MQ_PASSWORD"),
os.Getenv("RABBIT_MQ_HOST"),
os.Getenv("RABBIT_MQ_AMQP_PORT"))
rabbitManager, err := middleware.NewRabbitMQManager(amqpURL, os.Getenv("SERVER_PERFORMANCE_QUEUS"))
if err != nil {
log.Fatalf("RabbitMQManager の初期化に失敗: %v", err)
}
}
2. Kubernetes構成(20+pods)
# k8s/step1-infra/deployments/infra-redis-cluster.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
namespace: default
spec:
serviceName: redis-cluster
replicas: 6
template:
spec:
containers:
- name: redis
image: infra-cache-redis:latest
ports:
- containerPort: 6379
command:
- redis-server
args:
- --cluster-enabled yes
- --maxmemory 256mb
- --maxmemory-policy allkeys-lru
インフラサービス構成
- Redis Cluster: 6 pods(キャッシュ戦略)
- RabbitMQ: 3 pods(メッセージキュー)
- Kafka: 3 pods(イベントストリーミング)
- Prometheus: 1 pod(メトリクス収集)
- Grafana: 1 pod(モニタリング)
- 各マイクロサービス: 6+ pods
- MySQL: 各サービス専用DB
🚀 モノリシック構成(本番環境)
1. Docker Compose設定
# compose/docker-compose.prod.yml
services:
mysql:
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
backend:
env_file:
- .env
ports:
- ${REACT_APP_PRODUCTION_PORT}:${REACT_APP_PRODUCTION_PORT}
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
ui:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
nginx:
ports:
- "80:80"
- "443:443"
2. 統合されたルート構成
// services/api/product/routes/routes.go
package routes
func SetupRoutes(router *gin.Engine, db *gorm.DB, rabbitManager *middleware.RabbitMQManager) {
// 全マイクロサービスのコントローラーを統合
PaymentController := &controllers.PaymentController{DB: db}
// Route Group定義(元の境界を維持)
userRoutes := router.Group("/api/users")
planRoutes := router.Group("/api/plan")
billingRoutes := router.Group("/api/billing")
inboxRoutes := router.Group("/api")
// 統合されたエンドポイント
userRoutes.GET("/:id", controllers.GetUser)
userRoutes.POST("/register", controllers.RegisterUser)
planRoutes.GET("", controllers.GetUserPlan)
planRoutes.POST("/register/:plan_token", controllers.RebaseNewPlan)
billingRoutes.GET("", controllers.GetBillingInfo)
}
⚡ 自動変換システム
1. ルート統合スクリプト
#!/bin/bash
# scripts/reflect/merge_routes_dynamic.sh
echo "=== マイクロサービス→モノリシック ルート統合 ==="
SERVICE_DEVELOPMENT_DIR="$PROJECT_ROOT/services/api/development"
SERVICE_PRODUCT_DIR="$PROJECT_ROOT/services/api/product"
TARGET_FILE="$SERVICE_PRODUCT_DIR/routes/routes.go"
# 各マイクロサービスのroutes.goを検索
ROUTE_FILES=$(find "$SERVICE_DEVELOPMENT_DIR" -name "routes.go" -type f)
# 構造別に抽出
for file in $ROUTE_FILES; do
# 1. import文の抽出
awk '/^import \(/,/^\)/' "$file" | grep -E '^\s*[a-zA-Z_]*\s*"[^"]*"'
# 2. Controller定義の抽出
awk '/Controller.*:=.*&.*{/,/^[[:space:]]*}$/' "$file"
# 3. Route Group定義の抽出
grep -E 'router\.Group\(' "$file"
# 4. HTTPメソッド定義の抽出
grep -E '\.(GET|POST|PUT|DELETE|PATCH)\(' "$file"
done
# 統合されたroutes.goを生成
cat > "$TARGET_FILE" << 'EOF'
package routes
import (
// 統合されたimport文
)
func SetupRoutes(router *gin.Engine, db *gorm.DB, rabbitManager *middleware.RabbitMQManager) {
// 統合されたルート定義
}
EOF
2. Makefile環境切り替え
# Makefile抜粋
.PHONY: k8s-development-deploy product-build
# 開発環境(Kubernetes + マイクロサービス)
k8s-development-deploy-step1:
./scripts/development/deploy/kubernetes-deploy-step1-infra.sh
k8s-development-deploy-step2:
./scripts/development/deploy/kubernetes-deploy-step2-mysql.sh
k8s-development-deploy-step3:
./scripts/development/deploy/kubernetes-deploy-step3-backend.sh
# 本番環境(Docker Compose + モノリシック)
product-build:
./scripts/product/run-build.sh
./scripts/reflect/merge_routes_dynamic.sh
./scripts/reflect/merge_migrations_dynamic.sh
📊 パフォーマンス最適化戦略
1. キャッシュ戦略(Redis)
# Redis Cluster設定
args:
- --maxmemory 256mb
- --maxmemory-policy allkeys-lru
- --save "900 1" # 15分に1回保存
- --appendfsync everysec
2. モニタリング(Prometheus + Grafana)
# k8s/step1-infra/deployments/infra-prometheus.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
spec:
template:
spec:
containers:
- name: prometheus
image: infra-monitoring-prometheus:latest
ports:
- containerPort: 9090
volumeMounts:
- name: prometheus-config
mountPath: /etc/prometheus
3. メッセージキュー(RabbitMQ + Kafka)
- RabbitMQ: リアルタイム処理、パフォーマンスログ
- Kafka: イベントソーシング、大容量データストリーミング
💰 コスト比較
開発環境(Kubernetes)
- pods数: 20+
- AWS EKS想定コスト: ~$300-500/月
- 開発効率: ⭐⭐⭐⭐⭐
本番環境(Docker Compose)
- containers数: 5
- AWS EC2想定コスト: ~$50-100/月
- 運用コスト: ⭐⭐⭐⭐⭐
🔄 切り替えタイミング戦略
Phase 1: 収益 < $1000/月
- モノリシックで運用
- コスト最小化重視
Phase 2: 収益 $1000-5000/月
- パフォーマンスボトルネック監視
- 段階的マイクロサービス移行検討
Phase 3: 収益 > $5000/月
- フルマイクロサービス移行
- オートスケーリング導入
🛠️ 実装のポイント
1. サービス境界の明確化
// 各サービスで独立したDB接続
type UserService struct {
DB *gorm.DB
Redis *redis.Client
RabbitMQ *rabbitmq.Connection
}
// 明確なAPI契約
type UserAPI interface {
GetUser(id string) (*User, error)
CreateUser(user *User) error
UpdateUser(id string, user *User) error
}
2. 環境変数による設定切り替え
# .env
ENVIRONMENT=development # or production
REDIS_MODE=cluster # or single
DATABASE_MODE=distributed # or monolithic
3. CI/CDパイプライン
# .github/workflows/deploy.yml
jobs:
deploy-development:
if: github.ref == 'refs/heads/develop'
steps:
- name: Deploy to Kubernetes
run: make k8s-development-deploy
deploy-production:
if: github.ref == 'refs/heads/main'
steps:
- name: Build Monolithic
run: make product-build
- name: Deploy to Docker Compose
run: docker-compose -f compose/docker-compose.prod.yml up -d
📈 監視・アラート設定
Grafanaダッシュボード例
- レスポンス時間: 95パーセンタイル監視
- エラー率: 5%超過でアラート
- リソース使用率: CPU/メモリ使用率
- データベースパフォーマンス: スロークエリ監視
🎉 まとめ
このアーキテクチャにより:
✅ 初期コストを80%削減($500→$100/月)
✅ 開発効率を維持(マイクロサービスの利点)
✅ スケーラビリティを確保(瞬時切り替え可能)
✅ データ不整合リスクを最小化(事前境界設計)
スタートアップの成長フェーズに応じて柔軟にアーキテクチャを変更できる、実用的なソリューションです。