Возврат значений, выброс исключений и откат транзакций - PullRequest
1 голос
/ 19 сентября 2011

Много вопросов «когда генерировать исключение или возвращаемое значение» задавалось много (см. Ниже, чтобы увидеть только один пример):

Должен ли метод поиска возвращать 'null' или генерировать исключение, если он не может получить возвращаемое значение?

и я полностью согласен с ответами в основном.

Теперь мой вопрос возникает из-за добавления немного большего контекста к вышесказанному при применении этого к более сложной системе. Я постараюсь сделать это как можно более кратким и простым.

Хорошо, у нас есть пример приложения MVC PHP:

Модель A: имеет функцию get_car ($ id), которая возвращает объект автомобиля.

Контроллер А имеет простую функцию, например, для отображения автомобиля пользователю

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

Теперь мы переходим к основной части моего вопроса:

Для целостности данных я хочу использовать транзакции MySQL. Здесь я сталкиваюсь со сценарием «что лучше / что лучше» ...

Мы пишем Модель A, чтобы она возвращала FALSE, если автомобиль не найден или имеется ошибка SQL. Это нормально для контроллера A, так как он просто хочет знать, произошла ли ошибка и произошла ошибка, поэтому мы просто проверяем возвращаемое значение и bom - отлично.

Теперь мы переходим к контроллеру B. Контроллер B, скажем, выполняет некоторое обновление базы данных перед вызовом функции модели A, которую нам нужно откатить при ошибке, поэтому нам нужно использовать транзакции. теперь я дохожу до своей проблемы. оставить модель A в качестве возвращаемого значения и просто проверить его или изменить его, чтобы вызвать исключение с ударным эффектом, а затем переписать контроллер A, поскольку теперь нам нужно перехватить исключение ... then (не еще не сделано; о)) откатиться ли я в улове модели (но как мы узнаем, использовалась ли транзакция или нет?), или мы поймаем и перебросим или позволим всплывать до улова контроллера и сделать откат туда?

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

Итак, чтобы уточнить, я могу использовать try catch для перехвата и отката в контроллере, это нормально, но как мне сделать это «дальше вниз», например, в модели или в библиотечной функции ... которую можно вызвать как во время транзакции или просто как обычный вызов MySQL для автоматической фиксации?

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

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

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

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

Я надеюсь, вы получите, гдеЯ иду отсюда и прошу прощения за длинный нерешительный вопрос.

Заранее спасибо, Бен

1 Ответ

0 голосов
/ 16 августа 2013

[Есть теоретическая проблема, скрытая в «Для целостности данных, я хочу использовать транзакции MySQL» ... поскольку исторически MySQL не был очень ACID - PostgreSQL и Oracle оба обеспечивают более сильную поддержку ACID. Однако вернемся к реальному вопросу ...]

И ваши (1), и (2) фокусируются на исключениях, а не на значениях возврата, но у меня сложилось впечатление, что это не ключевая часть распутывания исключений, возвратов ошибок и открытых транзакций (а некоторые базы данных поддерживают исключения SQL также). Вместо этого я бы сосредоточился на сохранении состояния транзакции привязанным к вложению функций, управляющих моделью. Вот некоторые мысли по этому поводу:

  • В любом случае, вы, вероятно, всегда будете иметь возврат ошибок от некоторых библиотечных функций, поэтому возвращение FALSE Model A на самом деле не нарушает парадигму, и при этом нет ничего особенно неприятного в отношении сочетания ошибок и исключений. Однако возвращаемые ошибки ДОЛЖНЫ правильно всплывать - или преобразовываться в исключения, если они выходят за пределы локального адреса.
  • Вложенные транзакции - это наиболее очевидный способ заставить один контроллер запустить манипулирование базой данных и при этом вызывать другие приложения в приложении, которое также использует транзакции. Это позволяет сбойной подподфункции прервать только свою собственную транзакцию части и использовать либо метод возврата ошибки, либо исключительный подход для всплытия ошибки на стороне, отличной от SQL, в то время как закрытые суб-транзакции все еще поддерживают разумное состояние соответствия. Обычно это должно быть смоделировано в коде вне базы данных (это, по сути, то, что делает Django).
  • Ваш код может начать новую (потенциально большую) транзакцию и отслеживать тот факт, что он уже открыт, чтобы под-функции в вашем коде не пытались открыть его снова.
  • В некоторых базах данных код может определять, открыта ли уже транзакция на основе состояния сеанса базы данных, что позволяет вам проверять состояние сеанса БД, а не отслеживать его в коде.
  • Оба вышеперечисленных позволяют использовать точки сохранения для имитации действительно вложенных транзакций.
  • Нужно быть очень осторожным, чтобы не вызывать вызовы SQL с неявными фиксациями (например, CREATE TABLE). MySQL, вероятно, заслуживает гораздо большей осторожности в отношении этой проблемы, чем, скажем, PostgreSQL.

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

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

Надеюсь, это кому-нибудь поможет: -)

...