Існує велика спільнота людей, які використовують CQRS для реалізації своїх доменів. Я відчуваю, що якщо інтерфейс вашого сховища аналогічний найкращим практикам, які використовуються ними, ви не будете занадто заблукати.
На основі побаченого ...
1) Обробники команд зазвичай використовують сховище для завантаження агрегату через сховище. Команди орієнтуються на один конкретний екземпляр сукупності; сховище завантажує корінь за ідентифікатором. Як я бачу, не існує випадку, коли команди запускаються проти колекції агрегатів (замість цього, ви спершу запускаєте запит, щоб отримати колекцію агрегатів, а потім перераховуєте збірник і видаєте команду кожному.
Тому в контекстах, де ви збираєтеся змінювати сукупність, я б очікував, що сховище поверне сутність (він же корінь сукупності).
2) Обробники запитів взагалі не торкаються агрегатів; натомість вони працюють із проекціями агрегатів - об'єктів цінності, які описують стан агрегату / агрегатів у певний момент часу. Тож подумайте ProjectionDTO, а не AggregateDTO, і ви маєте правильну ідею.
У контекстах, де ви збираєтеся виконувати запити проти сукупності, готувати її до відображення тощо, я очікував би повернення DTO або колекції DTO, а не сутність.
Всі ваші getCustomerByProperty
дзвінки схожі на запити, щоб вони потрапили до останньої категорії. Я, мабуть, хотів би використати одну точку входу для створення колекції, тому я б з нетерпінням переконався, чи є
getCustomersThatSatisfy(Specification spec)
є розумним вибором; Потім обробники запитів побудують відповідну специфікацію з заданих параметрів і передадуть цю специфікацію в сховище. Мінусом є те, що підпис дійсно говорить про те, що сховище - це колекція в пам'яті; мені незрозуміло, що предикат купує вам багато, якщо репозиторій є лише абстракцією запуску оператора SQL проти реляційної бази даних.
Однак деякі моделі можуть допомогти. Наприклад, замість того, щоб скласти специфікацію вручну, передати в сховище опис обмежень і дозволити реалізації сховища вирішити, що робити.
Попередження: виявлено java типу введення тексту
interface CustomerRepository {
interface ConstraintBuilder {
void setLastName();
void setFirstName();
}
interface ConstraintDescriptor {
void copyTo(ConstraintBuilder builder);
}
List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}
SQLBackedCustomerRepository implements CustomerRepository {
List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
WhereClauseBuilder builder = new WhereClauseBuilder();
descriptor.copyTo(builder);
Query q = createQuery(builder.build());
//...
}
}
CollectionBackedCustomerRepository implements CustomerRepository {
List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
PredicateBuilder builder = new PredicateBuilder();
descriptor.copyTo(builder);
Predicate p = builder.build();
// ...
}
class MatchLastName implements CustomerRepository.ConstraintDescriptor {
private final lastName;
// ...
void copyTo(CustomerRepository.ConstraintBuilder builder) {
builder.setLastName(this.lastName);
}
}
На закінчення: вибір між наданням агрегату та наданням DTO залежить від того, що ви очікуєте від споживача. Моя здогадка - це одна конкретна реалізація, що підтримує інтерфейс для кожного контексту.
GetCustomerByName('John Smith')
повернеться, якщо у вашій базі буде двадцять Джона Сміта? Схоже, ви припускаєте, що жодна людина не має одного імені.