マイクロサービスアーキテクチャ設計完全ガイド:実装から運用まで|Docker・Kubernetes・Spring Boot対応
はじめに
マイクロサービスアーキテクチャは、大規模なアプリケーションを小さな独立したサービスに分割する設計手法です。Netflix、Amazon、Uberなどの大手企業が採用し、スケーラビリティと開発効率の向上を実現しています。
この記事では、マイクロサービスアーキテクチャの設計原則から実装方法、運用のベストプラクティスまで、実践的なコード例とともに詳しく解説します。
マイクロサービスアーキテクチャとは
マイクロサービスアーキテクチャは、アプリケーションを複数の小さな独立したサービスに分解し、各サービスが特定のビジネス機能を担当する設計パターンです。
マイクロサービスとモノリスの比較
| 特徴 | モノリス | マイクロサービス |
|---|---|---|
| デプロイ | 単一のアーティファクト | サービス個別デプロイ |
| スケーリング | 全体スケール | サービス個別スケール |
| 技術選択 | 統一技術スタック | サービス別技術選択 |
| 開発チーム | 大規模チーム | 小規模チーム |
| 複雑さ | 単純 | 分散システムの複雑さ |
1. サービス分割戦略
ドメイン駆動設計による分割
// User Service
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
User user = userService.createUser(request);
return ResponseEntity.status(201).body(user);
}
}
サービス境界の定義
// Order Service
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
Order order = orderService.createOrder(request);
return ResponseEntity.status(201).body(order);
}
@GetMapping("/user/{userId}")
public ResponseEntity<List<Order>> getUserOrders(@PathVariable String userId) {
List<Order> orders = orderService.findByUserId(userId);
return ResponseEntity.ok(orders);
}
}
2. サービス間通信
同期通信(HTTP/REST)
@Component
public class UserServiceClient {
private final RestTemplate restTemplate;
private final String userServiceUrl;
public UserServiceClient(RestTemplate restTemplate,
@Value("${services.user.url}") String userServiceUrl) {
this.restTemplate = restTemplate;
this.userServiceUrl = userServiceUrl;
}
public User getUserById(String userId) {
return restTemplate.getForObject(
userServiceUrl + "/api/users/" + userId,
User.class
);
}
}
非同期通信(メッセージング)
// RabbitMQ Producer
@Component
public class OrderEventPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
public void publishOrderCreated(OrderCreatedEvent event) {
rabbitTemplate.convertAndSend("order.exchange", "order.created", event);
}
}
// RabbitMQ Consumer
@RabbitListener(queues = "inventory.order.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
inventoryService.reserveItems(event.getOrderId(), event.getItems());
}
3. API Gateway設計
Spring Cloud Gateway実装
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.route("order-service", r -> r.path("/api/orders/**")
.uri("lb://order-service"))
.route("inventory-service", r -> r.path("/api/inventory/**")
.uri("lb://inventory-service"))
.build();
}
}
レート制限とセキュリティ
@Component
public class RateLimitFilter implements GatewayFilter {
private final RedisRateLimiter rateLimiter;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return rateLimiter.isAllowed("api", getUserId(exchange))
.flatMap(response -> {
if (response.isAllowed()) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
});
}
}
4. サービスディスカバリ
Eureka Server設定
# eureka-server application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
Eureka Client設定
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
# user-service application.yml
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
5. 設定管理
Spring Cloud Config Server
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
分散設定管理
# config-server application.yml
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-org/config-repo
search-paths: config
サービス固有設定
# user-service.yml (Git Repository)
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/userdb
username: ${DB_USERNAME:user}
password: ${DB_PASSWORD:password}
management:
endpoints:
web:
exposure:
include: health,info,metrics
6. 回復力パターン
Circuit Breaker実装
@Component
public class UserServiceClient {
@CircuitBreaker(name = "user-service", fallbackMethod = "getUserFallback")
@TimeLimiter(name = "user-service")
public CompletableFuture<User> getUserById(String userId) {
return CompletableFuture.supplyAsync(() ->
restTemplate.getForObject("/api/users/" + userId, User.class)
);
}
public CompletableFuture<User> getUserFallback(String userId, Exception ex) {
User fallbackUser = new User();
fallbackUser.setId(userId);
fallbackUser.setName("Unknown User");
return CompletableFuture.completedFuture(fallbackUser);
}
}
Retry機能
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public Order createOrder(CreateOrderRequest request) {
return orderRepository.save(new Order(request));
}
@Recover
public Order recover(Exception ex, CreateOrderRequest request) {
logger.error("Failed to create order after retries: {}", ex.getMessage());
throw new OrderCreationException("Unable to create order");
}
7. データ管理戦略
Database per Service
// User Service - MySQL
@Entity
public class User {
@Id
private String id;
private String name;
private String email;
// getters, setters
}
// Order Service - PostgreSQL
@Entity
public class Order {
@Id
private String id;
private String userId;
private BigDecimal amount;
private LocalDateTime createdAt;
// getters, setters
}
イベントソーシング
@Component
public class OrderEventStore {
public void saveEvent(String aggregateId, DomainEvent event) {
EventRecord record = new EventRecord();
record.setAggregateId(aggregateId);
record.setEventType(event.getClass().getSimpleName());
record.setEventData(JsonUtils.toJson(event));
record.setTimestamp(Instant.now());
eventRepository.save(record);
eventPublisher.publish(event);
}
public List<DomainEvent> getEvents(String aggregateId) {
return eventRepository.findByAggregateIdOrderByTimestamp(aggregateId)
.stream()
.map(this::deserializeEvent)
.collect(Collectors.toList());
}
}
8. コンテナ化とオーケストレーション
Dockerfile
FROM openjdk:11-jre-slim
COPY target/user-service-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
Docker Compose
version: '3.8'
services:
eureka-server:
image: eureka-server:latest
ports:
- "8761:8761"
user-service:
image: user-service:latest
ports:
- "8081:8080"
environment:
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka
depends_on:
- eureka-server
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: userdb
ports:
- "3306:3306"
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:1.0.0
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: mysql-service
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
9. 監視とロギング
分散トレーシング
@RestController
public class OrderController {
@Autowired
private UserServiceClient userServiceClient;
@NewSpan("create-order")
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
// ユーザー情報取得(別サービス呼び出し)
User user = userServiceClient.getUserById(request.getUserId());
// 注文作成処理
Order order = orderService.createOrder(request, user);
return ResponseEntity.status(201).body(order);
}
}
集約ログ管理
# logback-spring.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<mdc/>
<sleuthInfo/>
</providers>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
メトリクス収集
@Component
public class OrderMetrics {
private final Counter orderCounter;
private final Timer orderProcessingTime;
public OrderMetrics(MeterRegistry meterRegistry) {
this.orderCounter = Counter.builder("orders.created")
.description("Number of orders created")
.register(meterRegistry);
this.orderProcessingTime = Timer.builder("order.processing.time")
.description("Order processing duration")
.register(meterRegistry);
}
public void recordOrderCreated() {
orderCounter.increment();
}
public Timer.Sample startProcessingTimer() {
return Timer.start(orderProcessingTime);
}
}
10. セキュリティ
JWT認証
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}
OAuth 2.0 / OpenID Connect
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth-server.example.com
jwk-set-uri: https://auth-server.example.com/.well-known/jwks.json
11. テスト戦略
単体テスト
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private UserServiceClient userServiceClient;
@InjectMocks
private OrderService orderService;
@Test
void createOrder_ValidRequest_ReturnsOrder() {
// Given
CreateOrderRequest request = new CreateOrderRequest("user-123", 100.0);
User user = new User("user-123", "John Doe");
when(userServiceClient.getUserById("user-123")).thenReturn(user);
// When
Order result = orderService.createOrder(request);
// Then
assertEquals("user-123", result.getUserId());
verify(orderRepository).save(any(Order.class));
}
}
統合テスト
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class OrderIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("orderdb")
.withUsername("test")
.withPassword("test");
@Autowired
private TestRestTemplate restTemplate;
@Test
void createOrder_IntegrationTest() {
CreateOrderRequest request = new CreateOrderRequest("user-123", 100.0);
ResponseEntity<Order> response = restTemplate.postForEntity(
"/api/orders", request, Order.class);
assertEquals(201, response.getStatusCodeValue());
assertNotNull(response.getBody().getId());
}
}
12. DevOps と CI/CD
Jenkins Pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
}
}
stage('Package') {
steps {
sh 'mvn package'
archiveArtifacts artifacts: 'target/*.jar'
}
}
stage('Docker Build') {
steps {
sh 'docker build -t user-service:${BUILD_NUMBER} .'
sh 'docker push registry.example.com/user-service:${BUILD_NUMBER}'
}
}
stage('Deploy') {
steps {
sh 'kubectl set image deployment/user-service user-service=registry.example.com/user-service:${BUILD_NUMBER}'
}
}
}
}
マイクロサービス採用の判断基準
採用すべき場合
- 大規模チーム: 複数チームでの並行開発
- 異なる技術要件: サービスごとに最適な技術選択が必要
- 独立したスケーリング: サービスごとに異なる負荷特性
- ビジネス成長: 将来的な拡張を見据えた設計
避けるべき場合
- 小規模チーム: 管理コストが開発効率を上回る
- 単純なアプリケーション: 分散システムの複雑さが不要
- 運用経験不足: 分散システムの運用スキルが不足
ベストプラクティス
1. サービス設計
- 単一責任原則: 各サービスは一つのビジネス機能に集中
- 疎結合: サービス間の依存を最小限に抑制
- 高凝集: 関連する機能をサービス内に集約
2. データ管理
- Database per Service: 各サービスが独自のデータベースを持つ
- イベント駆動: 状態変更をイベントで通知
- 最終的整合性: 強整合性より可用性を重視
3. 運用
- 自動化: デプロイ、テスト、監視の自動化
- 監視: 包括的な監視とアラート体制
- 災害復旧: 障害時の復旧手順の整備
まとめ
マイクロサービスアーキテクチャは、適切に設計・実装されれば、スケーラビリティ、開発効率、技術選択の自由度において大きなメリットをもたらします。しかし、分散システムの複雑さやオーバーヘッドも伴うため、組織の規模や技術的成熟度を考慮して採用を判断することが重要です。
まずは小さなサービスから始めて、段階的にマイクロサービス化を進め、運用経験を蓄積しながら最適なアーキテクチャを構築していきましょう。
関連キーワード: マイクロサービス, マイクロサービスアーキテクチャ, Spring Boot マイクロサービス, Docker コンテナ, Kubernetes, サービスメッシュ, API Gateway, 分散システム, DevOps
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座



