← Назад к вопросам

Какая была архитектура у микросервисов, которые проектировал?

2.0 Middle🔥 151 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектура микросервисов, которые я проектировал

Разработал несколько систем на микросервисной архитектуре. Расскажу о наиболее удачном проекте.

Проект: Платформа для управления учебными заведениями

Стек: Java 17, Spring Boot, PostgreSQL, RabbitMQ, Docker, Kubernetes

Масштаб: 8-10 микросервисов, 2000+ одновременных пользователей

Архитектура на уровне сервисов

┌─────────────────────────────────────────────────────┐
│                   API Gateway                        │  (Spring Cloud Gateway)
│              (маршрутизация, rate limiting)         │
└────────┬────────────────────────────────────────────┘
         │
     ┌───┴────┬─────────────┬─────────────┬────────────┐
     │        │             │             │            │
  ┌──▼──┐  ┌──▼──┐      ┌──▼──┐      ┌──▼──┐     ┌───▼──┐
  │Auth │  │User │      │Post │      │File │     │Notify│
  │Svc  │  │Svc  │      │Svc  │      │Svc  │     │Svc   │
  └──┬──┘  └──┬──┘      └──┬──┘      └──┬──┘     └───┬──┘
     │        │            │            │            │
  ┌──▼─────┬──▼────┬──────▼┬───────┬───▼──┬─────────▼───┐
  │JWT     │User   │Post   │File   │Search│Notification│
  │Redis   │DB     │DB     │S3/Min │ES    │Queue RabbitMQ
  │        │       │       │IO     │      │
  └────────┴───────┴───────┴───────┴──────┴──────────────┘

1. Разделение ответственности (Domain-Driven Design)

Auth Service

Ответственность: Аутентификация и авторизация

// Выделен в отдельный сервис для:
// 1. Масштабирования (критичный сервис)
// 2. Безопасности (все проверки в одном месте)
// 3. Переиспользования (все сервисы на нём зависят)

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
    @PostMapping("/login")
    public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
        User user = userService.authenticate(request);
        String token = jwtProvider.generateToken(user);
        return ResponseEntity.ok(new TokenResponse(token));
    }
}

User Service

Ответственность: Управление профилями пользователей

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(userMapper.toDTO(user));
    }
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody CreateUserRequest request) {
        User user = userService.create(request);
        // Опубликуем событие для других сервисов
        eventPublisher.publishEvent(new UserCreatedEvent(user));
        return ResponseEntity.status(201).body(userMapper.toDTO(user));
    }
}

Post Service (основной бизнес-сервис)

Ответственность: Управление постами

@RestController
@RequestMapping("/api/v1/posts")
public class PostController {
    
    @PostMapping
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<PostDTO> createPost(@RequestBody CreatePostRequest request) {
        Post post = postService.create(request, getCurrentUserId());
        
        // Асинхронная индексация в ElasticSearch
        searchService.indexPost(post);
        
        // Уведомление для подписчиков
        notificationService.notifyFollowers(post.getAuthorId(), "New post");
        
        return ResponseEntity.status(201).body(postMapper.toDTO(post));
    }
}

2. Связь между сервисами

Синхронная коммуникация (REST/gRPC)

// Post Service делает запрос к User Service
@Service
public class PostService {
    private final UserServiceClient userServiceClient;
    
    public Post create(CreatePostRequest request, String authorId) {
        // Валидируем пользователя
        User author = userServiceClient.getUser(authorId);
        
        if (author == null) {
            throw new UserNotFoundException();
        }
        
        Post post = new Post();
        post.setAuthorId(authorId);
        post.setContent(request.getContent());
        post.setCreatedAt(LocalDateTime.now(UTC));
        
        return postRepository.save(post);
    }
}

// Resilience4j для обработки отказов
@Configuration
public class ResilienceConfig {
    @Bean
    public Resilience4jCircuitBreakerFactory circuitBreakerFactory() {
        return new Resilience4jCircuitBreakerFactory();
    }
}

// Использование:
@Service
public class PostService {
    @CircuitBreaker(name = "userService", fallback = "getUserFallback")
    public User getUser(String userId) {
        return userServiceClient.getUser(userId);
    }
    
    public User getUserFallback(String userId, Exception e) {
        // Возвращаем закэшированные данные или fail-safe ответ
        return userCache.getOrDefault(userId, null);
    }
}

Асинхронная коммуникация (Message Queue)

// Notification Service слушает события
@Component
public class NotificationListener {
    
    @RabbitListener(queues = "user.created")
    public void onUserCreated(UserCreatedEvent event) {
        String message = String.format("Welcome, %s!", event.getUserName());
        notificationService.send(event.getUserId(), message);
    }
    
    @RabbitListener(queues = "post.created")
    public void onPostCreated(PostCreatedEvent event) {
        // Отправить уведомление подписчикам
        List<String> followers = userService.getFollowers(event.getAuthorId());
        followers.forEach(follower -> 
            notificationService.send(follower, "New post from someone you follow")
        );
    }
}

// Post Service публикует событие
@Service
public class PostService {
    private final RabbitTemplate rabbitTemplate;
    
    public Post create(CreatePostRequest request, String authorId) {
        Post post = createPostInDB(request, authorId);
        
        // Публикуем асинхронное событие
        PostCreatedEvent event = new PostCreatedEvent(
            post.getId(),
            post.getAuthorId(),
            post.getContent()
        );
        
        rabbitTemplate.convertAndSend("post.created", event);
        
        return post;
    }
}

3. Данные и консистентность

База данных per сервис

Auth Service         User Service          Post Service
   ↓                    ↓                       ↓
 auth_db            user_db              post_db
(JWT tokens)      (profiles,          (posts,
                   credentials)        comments)

Преимущества:

  • Каждый сервис использует оптимальную БД
  • Независимый масштабирование
  • Слабая связанность

Вызовы:

// Проблема: транзакции между сервисами
// Решение: Saga pattern

@Service
public class UserRegistrationSaga {
    @Transactional
    public void registerUser(RegisterRequest request) {
        // 1. Создаём пользователя в User Service
        User user = userService.create(request);
        
        try {
            // 2. Создаём профиль в Profile Service
            profileService.create(user.getId());
            // ✓ Успех
        } catch (Exception e) {
            // ✗ Откатываем
            userService.delete(user.getId());
            throw e;
        }
    }
}

4. Кэширование

@Service
public class UserService {
    @Cacheable(value = "users", key = "#id")
    public User findById(String id) {
        return userRepository.findById(id);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void update(String id, UpdateUserRequest request) {
        User user = userRepository.findById(id);
        user.setName(request.getName());
        userRepository.save(user);
    }
}

// Redis для распределённого кэша
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.create(factory);
    }
}

5. Мониторинг и логирование

// Spring Cloud Sleuth для трассировки
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
    // Автоматически добавляется Trace ID и Span ID
    logger.info("Getting user {}", id);
    User user = userService.findById(id);
    return ResponseEntity.ok(userMapper.toDTO(user));
}

// Все логи связаны одним Trace ID:
// [app-1,abc123,span1] Getting user 123
// [app-2,abc123,span2] Fetching from DB
// [app-3,abc123,span3] Updating cache

// Prometheus метрики
@Metrics
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
    // Автоматически собираются метрики
    // - http_requests_total
    // - http_request_duration_seconds
    return ResponseEntity.ok(...);
}

6. Deployment (Kubernetes)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: post-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: post-service
  template:
    metadata:
      labels:
        app: post-service
    spec:
      containers:
      - name: post-service
        image: registry.example.com/post-service:1.0
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_DATASOURCE_URL
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: url
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

7. API Gateway

@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("auth", r -> r
                .path("/api/v1/auth/**")
                .uri("http://auth-service:8080")
            )
            .route("users", r -> r
                .path("/api/v1/users/**")
                .filters(f -> f.addRequestHeader("X-Service", "user-service"))
                .uri("http://user-service:8080")
            )
            .route("posts", r -> r
                .path("/api/v1/posts/**")
                .filters(f -> f.requestRateLimiter(
                    config -> config.setRateLimiter(redisRateLimiter())
                ))
                .uri("http://post-service:8080")
            )
            .build();
    }
}

Ключевые решения и проблемы

✓ Что сработало

  • Event-driven архитектура — сервисы слабо связаны
  • Кэширование — значительно улучшила производительность
  • Circuit breakers — предотвращают cascade failures
  • Kubernetes — простой deploy и масштабирование

✗ Что было сложно

  • Транзакции между сервисами — Saga pattern требует хорошего дизайна
  • Мониторинг — распределённые трассировки сложнее настраивать
  • Data consistency — eventual consistency требует иного мышления
  • Тестирование — интеграционные тесты между сервисами медленнее

Итоговая статистика

  • 8-10 микросервисов с чётким разделением ответственности
  • 2000+ одновременных пользователей при 99.9% uptime
  • Среднее время ответа: 150-200ms (включая сетевые задержки)
  • Deployment: Несколько раз в день без downtime

Эта архитектура позволила команде разрабатывать сервисы независимо и масштабировать критичные компоненты отдельно.