Когда бы использовал метод интеграции через базу данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Интеграция через базу данных: когда и почему
Интеграция через БД — паттерн, когда несколько приложений или сервисов обмениваются данными через общую базу данных, а не через API. Это спорный подход, но есть ситуации, где он оправдан.
Когда использовать БД интеграцию
1. Легаси системы без API
Если два системы работают в одной компании десятки лет и оба писали разные команды — переделать их на микросервисы слишком дорого:
// Старая система (1990s code)
public class LegacyOrderProcessor {
public void processOrder() {
// SELECT * FROM legacy_orders WHERE processed = false
// INSERT INTO shared_db.processed_orders
}
}
// Новая система (Java 2020+)
public class ModernBillingService {
public void syncOrders() {
// SELECT * FROM shared_db.processed_orders
// UPDATE billing_records
}
}
Плюсы:
- Не нужно переписывать legacy сервис
- Гарантированно консистентно (одна БД)
Минусы:
- Tight coupling между системами
- Сложнее масштабировать
- Shared db может стать bottleneck
2. Высокочастотная синхронизация данных
Если нужна абсолютная консистентность в real-time (не eventual consistency), и асинхронная интеграция не подходит:
// Сценарий: финансовый транспорт между счётами
// Нельзя потерять ни один рубль
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transferMoney(BigDecimal amount, Account from, Account to) {
// Обе системы видят изменение сразу
accountRepository.debit(from.getId(), amount);
// ... некий код второй системы ...
auditRepository.save(new Transaction(from, to, amount));
// COMMIT — обе системы согласованы
}
Когда это работает:
- Оба приложения контролируются одной командой
- Масштаб небольшой (< 10 request/sec)
- Требуется ACID гарантия
3. Reporting и Data Warehouse
Если нужно собрать данные из разных систем для аналитики:
@Component
public class ReportingIntegration {
@Scheduled(fixedRate = 3600000) // каждый час
public void syncDataToWarehouse() {
List<Order> orders = operationalDb.getAllOrders();
List<User> users = accountingDb.getAllUsers();
// Оба набора идут в reporting_db
warehouseRepository.saveOrders(orders);
warehouseRepository.saveUsers(users);
}
}
Это допустимо, потому что:
- Reporting data может быть eventual consistent (с delay)
- Isolation между operational и reporting
- Не влияет на production performance
4. Миграция системы (временно)
Когда переводишь data с одной системы на другую постепенно:
// Фаза 1: Dual-write
@Component
public class MigrationService {
public void createUser(User user) {
legacyDb.insertUser(user); // старая система
modernDb.insertUser(user); // новая система
}
}
// Фаза 2: Validation (смотрим, что данные совпадают)
// Фаза 3: Switch reads
// Читаем из modernDb, пишем в оба
// Фаза 4: Stop writing to legacy
// Полный переход
Это временное решение, но оно минимизирует риск.
Когда НЕ использовать
Хорошие альтернативы
1. REST API (или gRPC)
// Правильно: сервис A вызывает сервис B
@RestController
public class OrderController {
@PostMapping("/orders")
public Order createOrder(@RequestBody OrderRequest req) {
Order order = orderService.create(req);
billingClient.createInvoice(order); // API call
return order;
}
}
Плюсы: слабая связь (loose coupling), легко тестировать, масштабируется.
2. Message Queue (Kafka, RabbitMQ)
@Component
public class OrderCreatedListener {
@KafkaListener(topics = "orders")
public void onOrderCreated(Order order) {
billingService.createInvoice(order);
}
}
Плюсы: eventual consistency, асинхронность, отказоустойчивость.
3. Event Sourcing
// Event Store — источник истины
public class OrderEventStore {
public void append(OrderEvent event) {
database.insertEvent(event); // история всех изменений
}
public Order getState(String orderId) {
// Воспроизводим все события
return events.stream()
.filter(e -> e.getOrderId().equals(orderId))
.reduce(new Order(), (acc, evt) -> acc.apply(evt));
}
}
Плюсы: полная история, можно перестроить состояние, perfect audit trail.
Правило большого пальца
| Сценарий | Подход | Причина |
|---|---|---|
| Микросервисы | API / Message Queue | Независимость, масштабируемость |
| Один бизнес-домен | Shared database | Консистентность |
| Real-time синхронизация (финансы) | БД + ACID транзакции | Гарантированная консистентность |
| Асинхронные события | Kafka / Event Store | Отказоустойчивость, история |
| Аналитика | Reporting DB (ETL) | Isolate reporting из operational |
| Legacy интеграция | Temporal Database Integration | Минимум изменений в legacy |
Практический пример: когда я использовал
В проекте с финансовыми транспортами между счётами:
// Две системы: Billing и Accounting
// Оба имеют таблицу transactions в shared PostgreSQL
@Repository
public class SharedTransactionRepository {
@Transactional(isolation = Isolation.SERIALIZABLE)
public void recordTransfer(long fromId, long toId, BigDecimal amount) {
// Atmoic: обе системы видят одновременно
jdbcTemplate.update(
"UPDATE accounts SET balance = balance - ? WHERE id = ? FOR UPDATE",
amount, fromId);
jdbcTemplate.update(
"UPDATE accounts SET balance = balance + ? WHERE id = ? FOR UPDATE",
amount, toId);
// If anything fails — full rollback
}
}
Почему это сработало:
- Обе системы контролирует одна команда
- Масштаб маленький (< 100 транспортов/сек)
- Гарантия ACID критична
- Shared table снижает complexity vs Saga pattern
Вывод
Используй БД интеграцию ТОЛЬКО для:
- Legacy систем без API (временно)
- Одного bounded context с высокой связанностью
- Reporting/DW (с isolation)
- Операций, требующих ACID (финансы)
По умолчанию: используй REST API или Message Queue. Это лучшая практика в 95% случаев.