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.]
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Nemaye perevirenih versij ciyeyi storinki jmovirno yiyi she ne pereviryali na vidpovidnist pravilam proektu Command and Query Objects arhitekturnij shablon proyektuvannya zasnovanij na principi CQRS Zmist 1 Perevagi ta nedoliki 1 1 Perevagi 1 2 Nedoliki 2 Opis movoyu C 3 Div takozh 4 DzherelaPerevagi ta nedolikired Perevagired biznes logika podilyayetsya mizh ob yektami queries i commands Kod scenariyu vikoristannya inkapsulovanij v obrobniku obrobniki ne mistyat zajvih zalezhnostej legko realizovuvati naskriznu funkcionalnist oskilki vsi obrobniki mayut odnakovij interfejs queries vidpovidayut lishe za chitannya danih commands za zminu danih Zmenshuyetsya navantazhennya na vibirku danih legko zaminiti komponenti Yih regulyuvannyam zajmayetsya poserednik Nedolikired vazhkij v realizaciyi zbilshuyetsya kilkist klasivOpis movoyu C red Dodamo deyaki klasi yaki budut simulyuvati realni ob yekti public class DB dostup do bazi danih public class User ob yektno oriyentovane vidobrazhennya tablici v bazi danih Zapishemo interfejs query ta yiyi obrobnika Interfejs IQuery ne mistit niyakih viznachen Jogo budut realizovuvati dto klasi priznachennya yakih inkapsulyuvati dani ta peredati yih obrobnikam Inshimi slovami query vidigraye rol argumentiv funkciyi Zate vin mistit ochikuvanij rezultat povernennya takim chinom na jogo obrobnika nakladayutsya pevni obmezhennya Obrobnik dlya query IQueryHandler mistit lishe odin metod Handle Vin prijmaye argumenti ta povertaye neobhidnij rezultat public interface IQuery lt TResult gt public interface IQueryHandler lt TQuery TResult gt where TQuery IQuery lt TResult gt TResult Handle TQuery query Pripustimo sho vinikla zadacha znajti koristuvachiv za vvedenim znachennyam Todi Query i obrobnik mozhut mati nastupnij viglyad public class FindUsersBySearchTextQuery IQuery lt User gt public string SearchText get private set konstruktor ye obov yazkovoyu chastinoyu vin garantuye peredachu usih parametriv v Query public FindUsersBySearchTextQuery string searchText this SearchText searchText public class FindUsersBySearchTextQueryHandler IQueryHandler lt FindUsersBySearchTextQuery User gt DB db public FindUsersBySearchTextQueryHandler DB db this db db public User Handle FindUsersBySearchTextQuery query return db GetUser query SearchText Dekoli nemaye neobhidnosti v porodzheni velikoyi kilkosti klasiv obrobnikiv todi mozhna pomistiti vsi obrobniki v odin klas public class FindUsersBySearchTextQueryHandler IQueryHandler lt FindUsersBySearchTextQuery User gt IQueryHandler lt AllUserQuery User gt 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 Zalishilos napisati klas yakij bude spidstavlyati dlya query jogo handler public interface IQueryProcessor TResult Process lt TResult gt IQuery lt TResult gt query yaksho logika stvorennya obrobnika ye vazhkoyu todi cej metod docilno vinesti v okremij interfejs fabriki IDictionary lt Type Func lt object object gt gt RegistrateHandlers public sealed class QueryProcessor IQueryProcessor DB db IDictionary lt Type Func lt object object gt gt handlers public QueryProcessor DB db this db db this handlers RegistrateHandlers ne obov yazkovo public logiku spidstavlennya mozhna ne vinositi v interfejs a prihovati vid koristuvacha public IDictionary lt Type Func lt object object gt gt RegistrateHandlers IDictionary lt Type Func lt object object gt gt handlers new Dictionary lt Type Func lt object object gt gt spidstavlennya dlya zapitu vidpovidnogo obrobnika handlers typeof FindUsersBySearchTextQuery param gt new FindUsersBySearchTextQueryHandler db Handle param as FindUsersBySearchTextQuery return handlers vikonannya obrobnika zalezhno vid zapitu public TResult Process lt TResult gt IQuery lt TResult gt query return TResult handlers query GetType Invoke query Vikoristannya matime nastupnij viglyad IQueryProcessor queryProcessor new QueryProcessor new DB User filteredUsers queryProcessor Process new FindUsersBySearchTextQuery search text Rozrobka command maye tu samu strukturu Tilki priznachennyam command bude ne vzyattya danih a yih modifikaciyi stvorennya vidalennya tosho public interface ICommand lt out TResult gt public interface ICommandHandler lt in TCommand out TResult gt where TCommand ICommand lt TResult gt TResult Execute mozhlivo klas rezultatu public class CommandResponse public bool IsSucessed get set public string Message get set public interface ICommandProcessor TResult Process lt TResult gt ICommand lt TResult gt command IDictionary lt Type Func lt object object gt gt RegistrateHandlers Div takozhred Shabloni proyektuvannya programnogo zabezpechennya Ob yektno oriyentovane programuvannya CQRSDzherelared A Simple CQRS Pattern Using C in NET Arhivovano 21 chervnya 2019 u Wayback Machine On the query side of my architecture Arhivovano 11 listopada 2020 u Wayback Machine Otrimano z https uk wikipedia org wiki Command and Query Objects