Обработка ошибок и обратной связи при выполнении массовых операций в многоуровневой архитектуре

Скажем, у вас есть бизнес-логический метод, который может выполнять некоторую операцию по нескольким объектам. Возможно, вы хотите вызвать набор лотерейных номеров для веб-службы, один раз для каждого человека, выбранного из списка. В Java код может выглядеть примерно так:

Set<Person> selectedPeople = ... // fetch list of people
for ( Person person : selectedPeople ) {
    String lotteryNumber = callLotteryNumberWebService( person );
    // ...
}

Обратите внимание, что веб-служба номера лотереи может иметь побочные эффекты, например, запись о том, что человек запросил номер лотереи (возможно, взимать плату с их учетной записи), поэтому, даже если вызов веб-службы не срабатывает для одного человека, возможно, это удалось для другие. Эта информация (номера лотереи) должна быть возвращена на более высокий уровень (вид).

Если это был случай, когда выполнялась одна операция, метод бизнес-логики мог возвращать одно значение (например, номер лотереи) или выдавать исключение с любыми деталями отказа. Но для массовых операций было бы возможно, чтобы некоторые из операций преуспели, а некоторые из них потерпели неудачу.

Это похоже на проблему, которая возникает во многих приложениях, и должен быть чистый способ ее обработки. Итак, каков наилучший способ передать этот тип информации обратно с уровня бизнес-логики на другой уровень в приложении (например, представление), желательно общим способом, который может использоваться повторно для разных типов данных и операций?

Ответ 1

Если я понимаю, у вас есть ситуация, когда некоторые запросы могут пройти, а некоторые могут потерпеть неудачу. Я не уверен, где вы хотите, чтобы ошибка вернулась, но у вас может быть одно из следующих (или вариант или комбинация):

  • Список ошибок и затронутых объектов домена. Объект базового домена или что-то с устойчивым идентификатором может быть полезным для повторного использования. Например. набор ошибок, относящихся к объектам домена.
  • Вы можете ввести (AOP, DI) в объект Person какой-либо объект/сообщение об ошибке. Например. if (person. Errors) {...}
  • Вы можете обернуть коллекцию Person в сообщение с заголовком, телом, информацией об ошибках
  • Все ваши объекты домена могут включать в себя коллекцию ошибок, доступную через интерфейс; или Person поддерживает интерфейс IHasErrors. Вы можете сделать это общее и использовать базовый объект Error, поддерживающий предупреждения и валидацию и всевозможные вещи.

Если вы находитесь в подлинной многоуровневой (а не в многоуровневой) системе, тогда у вас может быть архитектура на основе сообщений, которая может легко разместить какой-то общий механизм ошибки/предупреждения/проверки. Системы SOA/Ajax поддаются этому.

Счастлив немного углубиться, если у вас есть какие-то конкретные вопросы.

Ответ 2

В этом вопросе подчеркиваются важные различия между соответствующими применениями обработки исключений, транзакций и идеей компенсации рабочего процесса, к которой стремится айзер, когда правильно заявляет:

Это похоже на проблему, которая возникает во многих приложениях, и должен быть чистый способ ее обработки.

Это обычная проблема, прежде всего, на фоне транзакционного подхода, который вы сейчас пытаетесь:

Операции с данными первоначально были смоделированы после учета двойного ввода - один кредит и соответствующий дебет должен был быть записан вместе или вообще не был. По мере того, как транзакции становятся больше, они становятся все более проблематичными для правильной реализации и сложнее справиться с отказом. Когда вы начинаете нести идею единой транзакции через границы системы, вы, скорее всего, приближаетесь к ней неправильно. Это можно сделать, но для этого требуются сложные и обязательные координаторы транзакций с более высокой задержкой. В определенном масштабе транзакции являются неправильным мышлением, и компенсация начинает приобретать гораздо больший смысл.

Здесь вы возвращаетесь и смотрите, что на самом деле делает бизнес. Единственная крупная сделка, скорее всего, не так, как ее видят деловые люди. Обычно они видят завершенный шаг, и в зависимости от последующих результатов могут потребоваться различные действия для компенсации. Именно здесь появляется идея рабочего процесса и компенсация. Вот одно введение в эти концепции

Например, если вы заказываете книгу из Amazon, они, вероятно, не "блокируют" запись, пока она находится в вашей корзине покупок, или даже используют строгие транзакции, чтобы определить, остается ли книга в запасе, когда заказ подтверждено. Они все равно продадут его вам и отправят, когда смогут. Если им не удалось получить его на складе в течение нескольких недель, они, вероятно, отправят вам электронное письмо с сообщением о том, что они пытаются удовлетворить ваши потребности, и вы можете продолжать ждать их, чтобы получить его на складе, или вы может отменить ваш заказ. Это называется компенсацией и необходимо во многих реальных бизнес-процессах.

Наконец, в этом нет ничего исключительного. Ожидайте, что это может произойти и использовать обычный поток управления. Вы не должны использовать здесь функции обработки исключений языка (правильные правила для того, когда нужно выбросить исключение). Также вы не должны полагаться на специальные инструменты (WCF?) Для просмотра или обработки исключений, которые происходят в реализации службы. Коммуникационные ошибки должны быть нормальной частью контракта с данными (контракты на неисправности).

К сожалению, с помощью "чистого способа справиться с этим" нет никакого флага для установки, который по волшебству позаботится об этом, вам нужно продолжать разлагать проблему и обрабатывать все полученные фрагменты. Надеемся, что эти концепции свяжут вас с тем, что сделали другие люди при решении этой проблемы.

Резюме:

  • Ваша проблема переросла концепцию транзакции → просмотрите компенсацию рабочего процесса.

Удачи -

Ответ 3

Я бы предпочел вернуть коллекцию созданных пользователем объектов ошибок, идентифицирующих объект, что вызвано ошибкой, кодом ошибки и описанием. Таким образом, ошибки можно попытаться исправить или отобразить дальше для пользователя.

Ответ 4

Я думаю, что вы действительно злоупотребляете исключениями, если вы думаете в этих терминах!

Вполне нормально возвращать значения, которые означают отказ, а не исключение. Часто это лучше. Исключения лучше всего использовать, когда вы не можете восстановить уровень абстракции, на котором вы находитесь, но вы не должны использовать их в качестве основного средства управления, или ваши программы будут очень трудно читать.

Веб-служба не возвращает исключения, возвращает коды возврата и сообщения. Я бы сохранил некоторое полезное представление, которое представляет возвращенную информацию, и вернет список тех, для просмотра или что-то, что будет смотреть на него.

Ответ 5

Я бы, вероятно, вернул карту результатов типа Map<Person,Future<String>> из моей службы getLotteryNumbers<Collection<Person>>.

Затем я перебираю карту и использую Future.get(), чтобы получить либо номер лотереи, либо брошенное исключение.

В некоторых моих сервисах мне нравится выполнять все вызовы как вызовы отдельных элементов, а затем иметь логику в моем сервисе для их пакетной обработки и обработки их как группы. Группировка выполняется с помощью LinkedBlockingQueue и потока опроса.

В этом сценарии я возвращаю Future<Thing>, ожидающий, что результаты пакета будут доступны с помощью CountdownLatch.

Взгляните на Java Concurrency на практике, чтобы увидеть, как эти компоненты могут работать вместе http://jcip.net/

Ответ 6

Я рассмотрел DTOs для такого рода задач. DTO также может включать в себя информацию о том, является ли упорство успешным или нет, и другими видами "метаданных".

Ответ 7

Другой способ, особенно для систем с высокой пропускной способностью, - использовать дизайн, основанный на очередях, в которых объект обработки будет выполнять операции над объектом, а затем на основе результатов помещает объект в разные очереди для дополнительной обработки другими объектами и затем двигайтесь дальше. Это уменьшит узкие места, которые возникнут из-за дополнительной обработки, которая потребуется, например. обработка заказа для продуктов на складе

Ответ 8

В идеале, вызов вашего веб-сервиса должен быть таким.

List<Person> selectedPeople = ... //fetch list of people
callLotteryNumberWebService(selectedPeople.ToArray );

Выполнение вызова веб-службы для каждого человека является дорогостоящим. На конце сервера вам необходимо выполнить итерацию по списку и выполнить операцию. Код на стороне сервера может вызывать 2 исключения: BulkOperationFailedException - если есть фатальная ошибка из-за отсутствия файла db или файла конфигурации. Дальнейшая обработка невозможна. BulkOperationException - это набор исключений, относящихся к человеку. Вы можете сохранить некоторый идентификатор для однозначного обращения к каждому объекту. Ваш код будет таким:

List<Person> selectedPeople = ... // fetch list of people 

try{
    callLotteryNumberWebService(selectedPeople.ToArray);
}catch  (BulkOperationFailedException e) {
    SOP("Some config file missing/db down.No person records processed")
}catch(BulkOperationException e)  {
    UserDefinedExceptions us =  e.getExceptions()
    foreach(exception ein us)   {
        // read unique id to find which person object failed
    }
}

construct msg based on which personobject succeeded and which failed

Операция считается успешной, когда не выбрасываются исключения. Вы можете создавать собственные коды ошибок для сбоев, а не использовать исключения UserDefined. Построение исключения BulkOperationException на стороне сервера является сложным. Во-вторых, вам нужно классифицировать ошибки, исходящие с серверной стороны, в BulkOperationFailedException и BulkOperationException. Так я работал в одном из моих проектов.