← Назад к вопросам
Как сделать так, чтобы два микросервиса обращались к одной и той же БД
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) — более явный и контролируемый