Command and Query Objects

Command and Query Objects — архітектурний шаблон проєктування заснований на принципі CQRS

Переваги та недоліки

Переваги

  • бізнес-логіка поділяється між об'єктами queries і commands. Код сценарію використання інкапсульований в обробнику
  • обробники не містять зайвих залежностей
  • легко реалізовувати наскрізну функціональність, оскільки всі обробники мають однаковий інтерфейс
  • queries відповідають лише за читання даних, commands — за зміну даних. Зменшується навантаження на вибірку даних
  • легко замінити компоненти. Їх регулюванням займається посередник

Недоліки

  • важкий в реалізації
  • збільшується кількість класів

Опис мовою C#

Додамо деякі класи, які будуть симулювати реальні об'єкти.

public class DB { ... } // доступ до бази даних
public class User { ... } // об'єктно-орієнтоване відображення таблиці в базі даних

Запишемо інтерфейс query та її обробника. Інтерфейс IQuery не містить ніяких визначень. Його будуть реалізовувати дто-класи призначення яких інкапсулювати дані, та передати їх обробникам. Іншими словами, query відіграє роль аргументів функції. Зате, він містить очікуваний результат повернення, таким чином на його обробника накладаються певні обмеження. Обробник для query — IQueryHandler — містить лише один метод — Handle. Він приймає аргументи, та повертає необхідний результат.

public interface IQuery<TResult> { }

public interface IQueryHandler<TQuery, TResult>
        where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

Припустимо, що виникла задача, знайти користувачів, за введеним значенням. Тоді Query і обробник можуть мати наступний вигляд:

public class FindUsersBySearchTextQuery : IQuery<User[]>
{
    public string SearchText { get; private set; }
        
    // конструктор, є обов'язковою частиною
    // він гарантує передачу усіх параметрів в Query
    public FindUsersBySearchTextQuery(string searchText)
    {
        this.SearchText = searchText;
    }
}

public class FindUsersBySearchTextQueryHandler
        : IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
    DB db;

    public FindUsersBySearchTextQueryHandler(DB db)
    {
         this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
         return db.GetUser(query.SearchText)
    }
}

Деколи немає необхідності в породжені великої кількості класів обробників, тоді можна помістити всі обробники в один клас:

public class FindUsersBySearchTextQueryHandler
        : IQueryHandler<FindUsersBySearchTextQuery, User[]>,
          IQueryHandler<AllUserQuery, User[]>
{
    DB db;

    public FindUsersBySearchTextQueryHandler(DB db)
    {
         this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
         return db.GetUsers(query.SearchText);
    }

    public User[] Handle(AllUserQuery query)
    {
         return db.GetAllUsers();
    }
}

Залишилось написати клас, який буде спідставляти для query його handler.

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);

    // якщо логіка створення обробника є важкою
    // тоді цей метод доцільно винести в окремий інтерфейс фабрики
    IDictionary<Type, Func<object, object>> RegistrateHandlers();
}
public sealed class QueryProcessor : IQueryProcessor
{
    DB db;
    IDictionary<Type, Func<object, object>> handlers;

    public QueryProcessor(DB db)
    {
        this.db = db;
        this.handlers = RegistrateHandlers();
    }
    // не обов'язково public 
    // логіку спідставлення можна не виносити в інтерфейс,
    // а приховати від користувача
    public IDictionary<Type, Func<object, object>> RegistrateHandlers()
    {
        IDictionary<Type, Func<object, object>> handlers = new Dictionary<Type, Func<object, object>>();
        
        // спідставлення для запиту відповідного обробника
        handlers[typeof(FindUsersBySearchTextQuery)] = (param) => new FindUsersBySearchTextQueryHandler(db).Handle(param as FindUsersBySearchTextQuery);
                                 .   .   .

        return handlers;
    }
        
    // виконання обробника, залежно від запиту
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        return (TResult)handlers[query.GetType()].Invoke(query);
    }       
        
}

Використання матиме наступний вигляд:

IQueryProcessor queryProcessor = new QueryProcessor(new DB());
User[] filteredUsers = queryProcessor.Process(new FindUsersBySearchTextQuery("search text"));

Розробка command має ту саму структуру. Тільки призначенням command буде не взяття даних, а їх модифікації, створення, видалення тощо.

public interface ICommand<out TResult> { }
public interface ICommandHandler<in TCommand, out TResult> where TCommand : ICommand<TResult>
{
    TResult Execute();
}
// можливо клас результату
public class CommandResponse
{
    public bool IsSucessed { get; set; }
    public string Message { get; set; }
}
public interface ICommandProcessor
{
    TResult Process<TResult>(ICommand<TResult> command);

    IDictionary<Type, Func<object, object>> RegistrateHandlers();
}

Див. також

Джерела

  • A Simple CQRS Pattern Using C# in .NET [Архівовано 21 червня 2019 у Wayback Machine.]
  • On the query side of my architecture [Архівовано 11 листопада 2020 у Wayback Machine.]
  • п
  • о
  • р
Основні шаблони
Абстрагування (програмування) • Делегування (Delegation) • Інтерфейс (Interface) • Інтерфейс-маркер (Marker Interface) • Незмінний інтерфейс (Immutable Interface) • Незмінний об'єкт (Immutable Object) • Функціональний дизайн (Functional Design) • Контейнер властивостей (Property Container) • Канал подій (Event Channel)
Твірні шаблони
Абстрактна фабрика (Abstract Factory) • Будівник (Builder) • Одинак (Singleton) • Прототип (Prototype) • Фабричний метод (Factory Method) • Пул об'єктів • Fluent builder • Мультитон • Лінива ініціалізація Отримання ресурсу, як ініціалізація (Resource Acquisition Is Initialization)
Структурні шаблони
Адаптер (Adapter) • Декоратор (Decorator) • Замісник (Proxy) • Компонувальник (Composite) • Міст (Bridge) • Легковаговик (Flyweight) • Фасад (Facade) • Модуль • Виділення приватного класу даних • Близнюки
Шаблони поведінки
Відвідувач (Visitor) • Інтерпретатор (Interpreter) • Ітератор (Iterator) • Команда (Command) • Ланцюжок відповідальностей (Chain of Responsibility) • Посередник (Mediator) • Спостерігач (Observer) • Стан (State) • Стратегія (Strategy) • Знімок (Memento) • Шаблонний метод (Template Method) • Одноразовий відвідувач • Null object • Специфікація • Feature toggleМультиметод • Перехоплювач (Interceptor) • Накопичувач (Collecting Parameter) • Слуга (Servant)
Функційні
Функтор • Генератор • Замикання • Монади • Каррінг • Функція зворотного виклику • Функція вищого порядкуВкладена функція • Результат (Result)
Патерни
конкурентного
програмування
Блокування • Модель акторів • Бар'єр • Монітор • Семафор • М'ютексПланувальник операційної системиЛокальна пам'ять ниток • Оптимістичне блокування (Optimistic Offline Lock) • Песимістичне блокування (Pessimistic Offline Lock) • Активний об'єкт (Active Object)
Кешування
Архітектурні
Базові шаблони
Клієнт-серверна архітектураFront end та back endТриярусна архітектура • Гексагональна архітектура (Архітектура портів та адаптерів) • Відокремлений інтерфейс (Separated Interface) • Сервісно-орієнтована архітектураМікросервісиPush/Pull модель
Шаблони об'єктного структурування
Rich/Anemic модельDAO • Command and Query Objects • DTO
Шаблони представлення
MVCPureMVCHMVCMVPMVVMPost/Redirect/Get
Шаблони предметно-орієнтованого проєктування
Rich/Anemic модельDDD • Інваріант • EntityValue ObjectAggregate RootDTORepositoryПатерн сервісного рівня (Service Layer) • Фабричний метод (Factory Method) • Специфікація
Шаблони сервісно-орієнтованої архітектури
Архітектура
корпоративних
програмних
додатків
Базові шаблони
Об'єкт-значення (Value Object) • Гроші (Money) • Особливий випадок (Special Case) • Супертип рівня (Layer Supertype) • Відокремлений інтерфейс (Separated Interface) • Шлюз (Gateway) • Розподільник (Mapper) • Реєстр (Registry) • Плагін (Plugin) • Набір записів (Record Set) • Заглушка сервісу (Service Stub)
Шаблони логіки домену
Сценарій транзакції (Transaction script) • Модель предметної області (Domain model) • Обробник таблиці (Table Module) • Патерн сервісного рівня (Service Layer)
Шаблони сховища даних
Активний запис (Active Record) • Шлюз до даних таблиці (Table Data Gateway) • Шлюз до даних запису (Row Data Gateway) • Відображення даних (Data Mapper)
Шаблони об'єктно-реляційної поведінки
Одиниця роботи (Unit Of Work) • Мапа відповідності (Identity Map) • Ліниве завантажування (Lazy Load)
Шаблони об'єктно-реляційного структурування
Поле первинного ключа (Identity Field) • Розмітка зовнішніх ключів (Foreign Key Mapping) • Розмітка зв'язків таблиць (Association Table Mapping) • Відображення залежних об'єктів (Dependent Mapping) • Об'єднане значення (Embedded Value) • Серіалізований великий об'єкт (Serialized LOB) • Наслідування з однією таблицею (Single Table Inheritance) • Наслідування з таблицею для кожного класу (Class Table Inheritance) • Наслідування з таблицею для кожного конкретного класу (Concrete Table Inheritance) • Відображення із наслідуванням (Inheritance Mappers) • База даних звітності
Шаблони обробки об'єктно-реляційних метаданих
Відображення на основі метаданих (Metadata Mapping) • Об'єкт-запит (Query Object) • Сховище (Repository)
Шаблони вебпредставлення
Модель-вид-контролер (Model View Controller) • Контролер сторінки (Page Controller) • Єдина точка входу (Front controller) • Контролер аплікації (Application Controller) • Шаблонізатор (Template View) • Перетворювач (Transform View) • Двокрокова шаблонізація (Two Step View)
Шаблони розподіленої обробки даних
Шаблони локального конкурентного програмування
Оптимістичне блокування (Optimistic Offline Lock) • Песимістичне блокування (Pessimistic Offline Lock) • Блокування із низьким рівнем деталізації (Coarse Grained Lock) • Неявне блокування (Implicit Lock)
Шаблони збереження стану сеансу
Збереження стану сеансу на стороні клієнта (Client Session State) • Збереження стану сеансу на стороні сервера (Server Session State) • Збереження стану сеансу в базі даних (Database Session State)
Тестування
PageObjectМакет об'єкта (Mock Object) • Заглушка сервісу (Service Stub) • Скромний об'єкт (Humble Object)
Інші
Впровадження залежностейIoC контейнер • Локатор служб (Service Locator) • М'яке видалення (Soft Delete) • Auditable Entity • Entity Component System (ECS)Extract, Transform, Load (ETL)
Див. також
Design Patterns (книга) • Бізнес-логіка • Інваріант • Зв'язність (Coupling) • Пов'язаність (Cohesion) • Закон ДеметриKISSDRYYAGNITell Don't Ask • SOLID • CQRSGRASPІдемпотентністьМартін ФаулерАнтипатерн