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

Как сделать так, чтобы два микросервиса обращались к одной и той же БД

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

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

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

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

Архитектура: несколько микросервисов и одна БД

Это распространённая архитектурная ситуация, особенно в монолитных системах, постепенно переходящих на микросервисы. Рассмотрим варианты реализации и их плюсы/минусы.

1. Прямой доступ обоих сервисов к одной БД

Реализация:

// service-a/application.yml
spring.datasource.url=jdbc:postgresql://db.internal:5432/shared_db
spring.datasource.username=app_user
spring.datasource.password=${DB_PASSWORD}

// service-b/application.yml
spring.datasource.url=jdbc:postgresql://db.internal:5432/shared_db
spring.datasource.username=app_user
spring.datasource.password=${DB_PASSWORD}
// Service A
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(String email) {
        User user = new User(email);
        return userRepository.save(user);
    }
}

// Service B
@Service
public class OrderService {
    @Autowired
    private UserRepository userRepository; // Обращается к той же БД
    
    public Order createOrder(String userId) {
        User user = userRepository.findById(userId).orElseThrow();
        // Проверяем, существует ли пользователь
        return new Order(user);
    }
}

Плюсы:

  • Просто реализуется
  • ACID транзакции работают
  • Консистентность данных

Минусы:

  • Высокая связанность сервисов
  • Сложно масштабировать независимо
  • Все сервисы используют одну сетевую точку
  • Изменения схемы БД требуют координации всех сервисов
  • Нет изоляции на уровне БД

2. Каждому сервису своя схема в одной БД

Более правильный подход — разделить таблицы по сервисам:

-- Schema for Service A
CREATE SCHEMA service_a;
CREATE TABLE service_a.users (
    id UUID PRIMARY KEY,
    email VARCHAR(255) UNIQUE,
    created_at TIMESTAMPTZ
);

-- Schema for Service B
CREATE SCHEMA service_b;
CREATE TABLE service_b.orders (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    total DECIMAL(10, 2),
    created_at TIMESTAMPTZ
);

-- Foreign key от Service B к Service A
ALTER TABLE service_b.orders 
ADD CONSTRAINT fk_user
FOREIGN KEY (user_id) 
REFERENCES service_a.users(id) ON DELETE CASCADE;
// Service A
@Entity
@Table(name = "users", schema = "service_a")
public class User {
    @Id
    private UUID id;
    private String email;
}

// Service B
@Entity
@Table(name = "orders", schema = "service_b")
public class Order {
    @Id
    private UUID id;
    
    @Column(name = "user_id")
    private UUID userId; // Ссылка на таблицу другого сервиса
    
    private BigDecimal total;
}

Плюсы:

  • Разделение ответственности
  • Четкие границы данных каждого сервиса
  • Изоляция на уровне таблиц

Минусы:

  • Всё ещё в одной БД (bottleneck)
  • Foreign key связи усложняют миграции

3. Правильный микросервисный подход: каждому своя БД

Это рекомендуемая архитектура:

Service A → Database A
Service B → Database B

Реализация:

// Service A: создаёт пользователей
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private KafkaTemplate<String, UserCreatedEvent> kafkaTemplate;
    
    @Transactional
    public User createUser(String email) {
        User user = userRepository.save(new User(email));
        
        // Уведомляем другие сервисы
        UserCreatedEvent event = new UserCreatedEvent(
            user.getId(), 
            user.getEmail()
        );
        kafkaTemplate.send("user-created-topic", event);
        
        return user;
    }
}

// Service B: слушает события Service A
@Service
public class OrderService {
    @Autowired
    private UserCacheService userCacheService;
    
    @KafkaListener(topics = "user-created-topic")
    public void onUserCreated(UserCreatedEvent event) {
        // Кешируем информацию о пользователе
        userCacheService.cache(event.getUserId(), event.getEmail());
    }
    
    public Order createOrder(String userId) {
        // Проверяем, знаем ли мы об этом пользователе
        UserInfo user = userCacheService.get(userId);
        if (user == null) {
            throw new UserNotFoundException("User " + userId);
        }
        return new Order(userId);
    }
}

Плюсы:

  • Сервисы полностью независимы
  • Масштабируются отдельно
  • Разные БД, разные технологии
  • Настоящая микросервисная архитектура

Минусы:

  • Сложнее реализовать
  • Нет ACID транзакций между сервисами
  • Нужна система обмена сообщениями (Kafka, RabbitMQ)
  • Возможна временная несконсистентность (eventually consistent)

4. Гибридный подход: shared БД + кеш

Когда нужна общая БД, но хотим избежать прямых обращений:

// Shared Database Access Service
@Service
public class SharedDataService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private CacheManager cacheManager;
    
    @Cacheable(value = "users", key = "#userId")
    public UserDto getUserById(UUID userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new UserNotFoundException());
        return UserDto.from(user);
    }
}

// Service A
@RestController
public class UserController {
    @Autowired
    private SharedDataService sharedDataService;
    
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody CreateUserRequest req) {
        // Service A владеет созданием
        return new ResponseEntity<>(HttpStatus.CREATED);
    }
}

// Service B
@RestController
public class OrderController {
    @Autowired
    private SharedDataService sharedDataService; // Только READ через сервис
    
    @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest req) {
        UserDto user = sharedDataService.getUserById(req.getUserId());
        // ...
        return new ResponseEntity<>(HttpStatus.CREATED);
    }
}

5. API-based подход

Вместо прямого доступа к БД, один сервис предоставляет API для других:

// Service A: экспортирует API
@RestController
@RequestMapping("/api/v1/users")
public class UserExternalController {
    @Autowired
    private UserRepository userRepository;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable UUID id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException());
        return ResponseEntity.ok(UserDto.from(user));
    }
}

// Service B: обращается к API Service A
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public Order createOrder(String userId) {
        ResponseEntity<UserDto> response = restTemplate.getForEntity(
            "http://service-a:8080/api/v1/users/" + userId,
            UserDto.class
        );
        
        if (!response.getStatusCode().is2xxSuccessful()) {
            throw new UserNotFoundException("User " + userId);
        }
        return new Order(response.getBody());
    }
}

Рекомендация

Для начального проекта:

  • Если 2 сервиса с одной БД — используй схемы (вариант 2)
  • Обрати внимание на связанность

Для растущей системы:

  • Переходи на каждому свою БД (вариант 3)
  • Используй Kafka/RabbitMQ для синхронизации
  • Кеш в каждом сервисе для часто запрашиваемых данных

Для критичных данных:

  • Используй API-подход (вариант 5) — более явный и контролируемый
Как сделать так, чтобы два микросервиса обращались к одной и той же БД | PrepBro