Конфликт возникает, когда несколько источников изменяют одни и те же
данные между моментами чтения и записи. В контексте Backbone.js это чаще
всего происходит при вызове Model.save() или
Collection.fetch(), когда состояние модели на клиенте
расходится с состоянием на сервере. Причины включают параллельные
изменения, задержки сети, офлайн-режим и повторные попытки
сохранения.
Backbone не навязывает стратегию разрешения конфликтов, предоставляя
минимальный слой синхронизации через Backbone.sync. Это
означает, что контроль целостности данных и согласование версий лежат на
разработчике и серверной логике.
Backbone.sync и точки расширенияBackbone.sync(method, model, options) — центральная
точка взаимодействия с сервером. Метод (create,
read, update, patch,
delete) и объект options позволяют внедрять
логику разрешения конфликтов.
Ключевые точки расширения:
Backbone.sync для
глобальной политикиModel.sync для
точечной настройкиsuccess и error
для анализа ответа сервераПример локального переопределения:
var BaseModel = Backbone.Model.extend({
sync: function(method, model, options) {
options = options || {};
options.headers = options.headers || {};
options.headers['If-Match'] = model.get('etag');
return Backbone.sync.call(this, method, model, options);
}
});
Наиболее распространённый подход — оптимистическая блокировка с
использованием версии объекта. Сервер хранит поле версии
(version, UPDATEdAt, etag) и
отклоняет обновление, если версия клиента устарела.
Клиентская модель:
var Document = Backbone.Model.extend({
defaults: {
version: 0
}
});
Сервер при обновлении сравнивает версию из запроса с текущей. В
случае несоответствия возвращается, например,
409 Conflict.
Обработка конфликта на клиенте:
doc.save(data, {
error: function(model, response) {
if (response.status === 409) {
model.se t(response.responseJSON.current);
}
}
});
ETag — более формализованный механизм версионирования на уровне HTTP.
Сервер возвращает заголовок ETag, клиент сохраняет его в
модели и отправляет в If-Match.
Преимущества:
Недостаток — необходимость строгой поддержки на сервере.
Интеграция в Backbone обычно реализуется через parse и
sync:
parse: function(response, options) {
if (options.xhr) {
this.set('etag', options.xhr.getResponseHeader('ETag'));
}
return response;
}
Метод patch отправляет только изменённые поля, что
уменьшает область конфликта.
model.save({ title: 'New' }, { patch: true });
В сочетании с серверной логикой, которая обновляет только переданные поля, это снижает вероятность перезаписи несвязанных изменений.
Важно учитывать, что patch не решает конфликты
полностью, но уменьшает их последствия.
wait и согласованность состоянияwait: true откладывает обновление модели до
подтверждения сервера.
model.save(data, { wait: true });
Без wait модель оптимистично обновляется сразу, что
усложняет откат при конфликте. С wait состояние клиента
всегда подтверждено сервером, но интерфейс становится менее
отзывчивым.
Комбинированный подход часто включает локальное временное состояние и последующую замену подтверждёнными данными.
Сервер может играть активную роль в резолюции:
Backbone легко адаптируется ко всем стратегиям, так как не накладывает ограничений на формат ответа.
При получении конфликта сервер может вернуть обе версии: текущую и предложенную. Клиент объединяет их по правилам доменной логики.
error: function(model, response) {
var serverData = response.responseJSON.current;
var localData = model.toJSON();
var merged = _.extend({}, serverData, localData);
model.set(merged);
}
Такой подход требует строгих правил приоритета полей и аккуратной работы с побочными эффектами.
При Collection.fetch() конфликты проявляются как
несогласованность элементов. Backbone по умолчанию использует
id для сопоставления моделей.
Параметры:
reset: true — полная замена коллекцииmerge: true — слияние по idcollection.fetch({ merge: true });
При конфликте версий внутри коллекции полезно переопределять
Model.parse и сравнивать версии перед применением
данных.
При офлайн-работе конфликты неизбежны. Типовой подход:
dirtyBackbone не содержит встроенного механизма офлайн-синхронизации, но
его событийная модель (change, sync,
error) упрощает реализацию очередей и повторных
попыток.
Рекомендуется рассматривать конфликт как состояние модели, а не как исключение.
model.on('error', function(model, response) {
if (response.status === 409) {
model.set('conflict', true);
}
});
Это позволяет реактивно перестраивать логику приложения и централизовать обработку расхождений данных.
Эффективная резолюция конфликтов в Backbone требует согласованной архитектуры:
Backbone предоставляет гибкий, но низкоуровневый фундамент, что делает конфликт-резолюцию не встроенной функцией, а архитектурным решением, интегрированным в модель данных и протокол взаимодействия.