Как разделяется логика чтения и записи данных в приложении
Что проверяют
Различаете ли вы уровни разделения, от правила для методов до отдельных моделей и хранилищ, и понимаете ли, чем платите за каждый шаг. Следом обычно спрашивают про согласованность: что увидит пользователь, если читать с реплики сразу после записи.
Как отвечать
Идите от простого к сложному:
- CQS: метод либо меняет состояние, либо возвращает данные, но не то и другое сразу. Правило для кода без всякой инфраструктуры;
- сервисный слой: запросы помечены
@Transactional(readOnly = true). Hibernate в таком режиме не делает dirty checking и не сбрасывает изменения, а драйвер может передать базе признак только чтения; - модели чтения: для записи сущности JPA с инвариантами, для чтения DTO через проекции Spring Data или
recordв выраженииSELECT new .... Чтение не тянет граф сущностей и не страдает от N+1; - реплика:
AbstractRoutingDataSourceсмотрит на флаг read-only текущей транзакции и отправляет чтения на реплику. Оборачивать его нужно вLazyConnectionDataSourceProxy, иначе соединение берётся до того, как флаг выставлен, и всё уходит на мастер; - полный CQRS: команды пишут в основную базу, события через outbox и Kafka обновляют отдельную модель чтения, например индекс Elasticsearch или денормализованные таблицы. Чтение масштабируется независимо, но согласованность только в конечном счёте.
Скажите, где остановились бы сами: большинству сервисов хватает второго и третьего пункта, а полный CQRS оправдан при сильно разной нагрузке на чтение и запись или сложных представлениях данных.
Где спотыкаются
Отождествляют CQRS с event sourcing. Их часто используют вместе, но одно не требует другого.
Забывают про задержку репликации. Пользователь сохранил профиль, обновил страницу и видит старые данные: чтение своих только что записанных данных надо направлять на мастер.
Разводят интерфейсы UserQueryRepository и UserCommandRepository, а реализует их один класс с одной моделью. Формально разделение есть, по сути ничего не изменилось.