Нарушение разделения командного запроса
О чем вы думаете
if(!DoSomething()) return;
В чистом коде это рассматривается как нарушение разделения командных запросов. Но как понять, что что-то в команде DoSomething() пошло не так? А как насчет команды sql (например, void Delete(Table))? Как мы можем узнать, существует ли эта таблица?
Благодарю.
4 ответа
Я согласен с комментариями rObiwahn, что вы должны проверить CanDoSomething
перед выдачей команды DoSomething
, В чистой среде CQRS, DoSomething
не вернул бы ничего, и если бы что-то помешало Нечто произошло (не из-за исключения, а из-за расы или чего-то другого, изменяющегося между CanDoSomething
а также DoSomething
), ваш домен будет выдавать DoSomethingWasInvalid
событие (или что-то подобное), которое позволит вашему приложению в конечном итоге стать согласованным.
Это может показаться сложным, но на самом деле все становится довольно просто, когда вы начнете разбивать логику на маленькие кусочки и позволить вашему приложению принять возможную согласованность.
Есть много хороших ресурсов по группе DDD/CQRS в группах Google. Вопрос типа " Как сообщить отправителю, что команда не выполнена?'немного похож на ваш вопрос. Такие люди, как Уди Дахан, Грег Янг, Ринат Абдуллин и другие, следят за этой группой и дают действительно отличные ответы. Я бы порекомендовал проверить это время от времени тоже.
Надеюсь это поможет!
Я напишу этот пример на Eiffel, чтобы его было проще понять.
my_code
-- Calls the `do_something' routine
do
set_table ("my_table")
do_something
end
do_something
-- Something to do
require
valid_table: is_valid_table (table_name)
do
sql_list := execute_sql_on_table (table_name)
ensure
has_result: sql_list.count > 0
end
sql_list: ARRAYED_LIST [STUFF]
table_name: STRING
set_table (a_name: STRING)
-- Set `table_name' to `a_name'
require
has_name: not a_name.is_empty
valid_table: is_valid_table (a_name)
do
table_name := a_name
ensure
table_name_set: table_name.same_string (a_name)
end
delete_table (a_name: STRING)
-- Delete `a_name' from the database.
require
valid_table: is_valid_table (a_name)
do
execute_sql ("DROP TABLE " + a_name)
ensure
table_gone: not is_valid_table (a_name)
end
- Функция do_something - это команда, в которой массив sql_list должен быть загружен с помощью STUFF из таблицы "my_table".
- Предварительный договор на
do_something' makes it the responsibility of the client
my_code'для предоставленияtable_name' before making the call to
сделай что-нибудь'. - В свою очередь, контракт на пост-условие обеспечивает ответственность поставщика
do_something' fill the array
sql_list'с экземплярами STUFF. - Функция sql_list представляет собой запрос, возвращающий ссылочный указатель на массив STUFF.
- Точно так же особенность
table_name' is a query returning a reference pointer to a STRING, which is set with a "setter" command called
set_table.
В этом случае "контракты" Design-by-Contract позаботятся о том, чтобы обеспечить надлежащее разделение интересов и кто за что отвечает в этом небольшом фрагменте кода выше. Обратите внимание на явное отсутствие конструкций TRY-CATCH в коде. В этом случае ожидается, что источник данных будет иметь "my_table". Наличие контрактов означает, что программное обеспечение вызовет исключение, когда контракт не будет выполнен. Сбой по требованию говорит о том, что вызывающий абонент неисправен, а сбой в проверке после условия указывает на функцию поставщика.
Наконец, этот код демонстрирует четкое разделение команд и запросов и применение гарантии качества, полученной при проектировании по контракту. Таким образом, на оригинальный вопрос можно ответить:
"Но как мы можем понять, что что-то в команде DoSomething() пошло не так? Как насчет команды sql (например, void Delete(Table))? Как мы можем узнать, существует ли эта таблица?"
Хотя это может быть правдой, что вызов delete_table ("my_table") может быть внедрен в какого-то предка или может произойти в другом потоке, но для этого и нужны контракты. do_something'. As long as those contracts stand guard over calls to
do_something', процесс будет соответствующим образом обработан. Инжектированный вызов `delete_table'просто приведет к сбою контракта.
Все это предполагает, что НЕЛЬЗЯ БРОСАТЬ ТАБЛИЦУ на "my_table", и это трагически случайно. Тем не менее, если для "my_table" становится ОК для DROP TABLE, то для управления этим вариантом использования необходим механизм повтора или другой "обработчик", и приведенный выше код не будет работать.
Если что-то пошло не так, DoSomething()
Вероятно, следует сгенерировать исключение, если вы, как вызывающая сторона, должны его обработать
Например:
try
{
DoSomething();
// .. do more after success
}
catch(SomeException ex) // maybe focus on a special error
{
// maybe do something special or just clean up!
}
Мне действительно пришла в голову эта вещь сейчас [почему мы не думаем о "проблемах разделения", а не "разделения интересов"!] Я знаю, что это не по теме, но слишком сильный толчок для стандартов не приведет ни к чему. Из истории человечества легко увидеть, сколько практики / стандартов оказались неверными со временем! это все о вашей перспективе превосходства.
поэтому в таком случае я всегда стараюсь думать об обратном, чтобы оставаться РЕАЛЬНЫМ. Я хочу сказать больше, но я думаю, что этого достаточно с моей точки зрения, чтобы ответить;)
удачи!