diff --git a/_data/sidebars/flexberry-orm_sidebar.yml b/_data/sidebars/flexberry-orm_sidebar.yml index 7b1bcf204..9c7c16d17 100644 --- a/_data/sidebars/flexberry-orm_sidebar.yml +++ b/_data/sidebars/flexberry-orm_sidebar.yml @@ -728,6 +728,10 @@ entries: title_ru: Особенности определения загруженности свойств url: /fo_definition-loaded-properties.html output: web + - title: System of notifications about object updates + title_ru: Система уведомлений об обновлении объектов + url: /fo_object-update-notification.html + output: web - title: ISpecialEmptyValue title_ru: ISpecialEmptyValue url: /fo_i-special-empty-value.html diff --git a/pages/products/flexberry-orm/additional-features/fo_object-update-notification.en.md b/pages/products/flexberry-orm/additional-features/fo_object-update-notification.en.md new file mode 100644 index 000000000..ee5b1d6f8 --- /dev/null +++ b/pages/products/flexberry-orm/additional-features/fo_object-update-notification.en.md @@ -0,0 +1,310 @@ +--- +title: System of notifications about object updates +sidebar: flexberry-orm_sidebar +keywords: Flexberry ORM, notification, object update +summary: Mechanism for executing logic at various stages of the data modification lifecycle +toc: true +permalink: en/fo_object-update-notification.html +lang: en +--- + +The system of notifications about object updates provides a mechanism for executing logic at various stages of the data modification lifecycle. Notifications allow you to react to events of creation, modification and deletion of objects at three levels: + +- **INotifyUpdateObject** - at the level of the entire object +- **INotifyUpdateProperty** - at the level of individual object properties +- **INotifyUpdateObjects** - centralized manager for multiple objects + +## Notification levels + +### INotifyUpdateObject + +Interface for notifications at the level of a separate object: + +```csharp +public interface INotifyUpdateObject +{ + void BeforeUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterSuccessSqlUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterSuccessUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterFailUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); +} +``` + +### INotifyUpdateProperty + +Interface for notifications at the level of object properties: + +```csharp +public interface INotifyUpdateProperty +{ + void BeforeUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterSuccessSqlUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterSuccessUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterCommitUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterFailUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); +} +``` + +### INotifyUpdateObjects + +Centralized notification manager: + +```csharp +public interface INotifyUpdateObjects +{ + void BeforeUpdateObjects(Guid operationId, IDataService dataService, IDbTransaction transaction, IEnumerable dataObjects); + void AfterSuccessSqlUpdateObjects(Guid operationId, IDataService dataService, IDbTransaction transaction, IEnumerable dataObjects); + void AfterSuccessUpdateObjects(Guid operationId, IDataService dataService, IEnumerable dataObjects); + void AfterCommitUpdateObjects(Guid operationId, IDataService dataService, IEnumerable dataObjects); + void AfterFailUpdateObjects(Guid operationId, IDataService dataService, IEnumerable dataObjects); + void CleanupStateStore(Guid operationId); +} +``` + +## Update lifecycle + +### Successful update + +```text +BeforeUpdateObjects + └── Start processing, collect changes + +SQL-queries (INSERT/UPDATE/DELETE) + └── Execute queries to the database + +AfterSuccessSqlUpdateObjects + └── Notification after SQL, but before commit + +AfterSuccessUpdateObjects + └── Notification after processing + +COMMIT TRANSACTION + └── commit changes to the database + +AfterCommitUpdateObjects + └── notification after guaranteed saving + +CleanupStateStore + └── Memory cleanup +``` + +### Update with error + +```text +BeforeUpdateObjects + └── Start processing + +SQL-queries (INSERT/UPDATE/DELETE) + └── Execute queries to the database + +ROLLBACK TRANSACTION + └── rollback the transaction + +AfterFailUpdateObjects + └── Notification of failure + +CleanupStateStore + └── Memory cleanup (prevents leaks) +``` + +## Usage scenarios + +### Sending notifications + +After successful commit, you can safely send email, SMS or push notifications: + +```csharp +public class Order : DataObject, INotifyUpdateObject +{ + public string Email { get; set; } + public decimal Amount { get; set; } + + public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) + { + var order = (Order)dataObject; + + if (status == ObjectStatus.Created || status == ObjectStatus.Altered) + { + var emailService = DependencyResolver.Current.GetService(); + emailService.SendOrderConfirmation(order.Email, order.Amount); + } + } +} +``` + +### Integration with external systems + +Calling external API after guaranteed write to the database: + +```csharp +public class Product : DataObject, INotifyUpdateObject +{ + public string ExternalId { get; set; } + public string Name { get; set; } + + public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) + { + var product = (Product)dataObject; + + var syncService = DependencyResolver.Current.GetService(); + + switch (status) + { + case ObjectStatus.Created: + syncService.CreateProduct(product.Id, product.Name); + break; + case ObjectStatus.Altered: + syncService.UpdateProduct(product.Id, product.Name); + break; + case ObjectStatus.Deleted: + syncService.DeleteProduct(product.Id); + break; + } + } +} +``` + +### Cache invalidation + +Invalidate cache only after changes are committed: + +```csharp +public class Customer : DataObject, INotifyUpdateObject +{ + public string Name { get; set; } + + public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) + { + var cache = DependencyResolver.Current.GetService(); + cache.Remove($"customer_{dataObject.__PrimaryKey}"); + } +} +``` + +### Property level + +For properties of type that implements `INotifyUpdateProperty`: + +```csharp +public class ComplexProperty : INotifyUpdateProperty +{ + public string Value { get; set; } + + public void AfterCommitUpdateProperty( + DataObject dataObject, + ObjectStatus status, + string propertyName, + object oldValue, + object newValue) + { + var complexProp = (ComplexProperty)newValue; + Logger.Info($"Property {propertyName} changed to {complexProp.Value}"); + } +} +``` + +## Memory cleanup + +The `CleanupStateStore` method is called on errors to remove the internal state of the operation. This prevents memory leaks: + +```csharp +public virtual void CleanupStateStore(Guid operationId) +{ + stateStore.Remove(operationId); +} +``` + +## Centralized notification manager + +The notification manager is configured through `NotifierUpdateObjects`: + +```csharp +var dataService = new SQLDataService(); +dataService.NotifierUpdateObjects = new NotifierUpdateObjects(); +``` + +For property handling, you can use a custom implementation: + +```csharp +dataService.NotifierUpdateObjects = new NotifierUpdateObjects( + new CustomPropertyNotifier()); +``` + +## Error handling + +In post-commit methods, exceptions are not thrown - the transaction has already been committed. Error handling is done through try/catch: + +```csharp +public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) +{ + try + { + SendEmail(...); + } + catch (Exception ex) + { + logger.Error("Failed to send email after commit", ex); + } +} +``` + +## Object statuses + +The state of the object is passed through `ObjectStatus`: + +- **Created** - object created +- **Altered** - object modified +- **Deleted** - object deleted + +The difference of operations is possible by the value of the status: + +```csharp +if (status == ObjectStatus.Deleted) +{ + // Handling deletion +} +``` + +## Principles of operation + +### Internal state store + +The internal store (`stateStore`) stores the state of changes for each operation: + +- By `operationId` - operation identifier +- By `Type` - object type +- By `PrimaryKey` - primary key of the object +- By `PropertyName` - property name + +Each stored value is a `Tuple`: status, old and new value. + +### Call order + +1. **BeforeUpdateObjects** - collect changes before SQL-queries +2. **AfterSuccessSqlUpdateObjects** - after SQL execution, before COMMIT +3. **AfterSuccessUpdateObjects** - after processing, before COMMIT +4. **AfterCommitUpdateObjects** - after guaranteed commit +5. **AfterFailUpdateObjects** - on transaction rollback + +## Notifications in the system + +The notification system is integrated into `SQLDataService` and automatically calls the appropriate methods at all stages of the update lifecycle. Key components: + +- **NotifierUpdateObjects** - standard implementation of `INotifyUpdateObjects` +- **DbTransactionWrapper** - wraps the transaction, tracks operation identifiers +- **StateStore** - internal state store + +## Recommendations + +The behavior of notification methods is regulated by the following principles: + +- **AfterCommitUpdateObjects** - for actions requiring guaranteed saving (email, API, logs) + +- **AfterSuccessUpdateObjects** - for operations without requiring commit (internal validation, caching at application level) + +- Avoid long-running operations in post-commit methods + +- Errors in post-commit are handled through try/catch; exceptions are not thrown - the transaction has already been committed + +- Notifications may come for deleted objects; status is checked through `ObjectStatus` diff --git a/pages/products/flexberry-orm/additional-features/fo_object-update-notification.ru.md b/pages/products/flexberry-orm/additional-features/fo_object-update-notification.ru.md new file mode 100644 index 000000000..067b60f9b --- /dev/null +++ b/pages/products/flexberry-orm/additional-features/fo_object-update-notification.ru.md @@ -0,0 +1,310 @@ +--- +title: Система уведомлений об обновлении объектов +sidebar: flexberry-orm_sidebar +keywords: Flexberry ORM, уведомление, обновление объекта +summary: Механизм для выполнения логики на различных этапах жизненного цикла изменения данных +toc: true +permalink: ru/fo_object-update-notification.html +lang: ru +--- + +Система уведомлений об обновлении объектов предоставляет механизм для выполнения логики на различных этапах жизненного цикла изменения данных. Уведомления позволяют реагировать на события создания, изменения и удаления объектов на трёх уровнях: + +- **INotifyUpdateObject** — на уровне целого объекта +- **INotifyUpdateProperty** — на уровне отдельных свойств объекта +- **INotifyUpdateObjects** — централизованный менеджер для нескольких объектов + +## Уровни уведомлений + +### INotifyUpdateObject + +Интерфейс для уведомлений на уровне отдельного объекта: + +```csharp +public interface INotifyUpdateObject +{ + void BeforeUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterSuccessSqlUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterSuccessUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); + void AfterFailUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects); +} +``` + +### INotifyUpdateProperty + +Интерфейс для уведомлений на уровне свойств объекта: + +```csharp +public interface INotifyUpdateProperty +{ + void BeforeUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterSuccessSqlUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterSuccessUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterCommitUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); + void AfterFailUpdateProperty(DataObject dataObject, ObjectStatus status, string propertyName, object oldValue, object newValue); +} +``` + +### INotifyUpdateObjects + +Централизованный менеджер уведомлений: + +```csharp +public interface INotifyUpdateObjects +{ + void BeforeUpdateObjects(Guid operationId, IDataService dataService, IDbTransaction transaction, IEnumerable dataObjects); + void AfterSuccessSqlUpdateObjects(Guid operationId, IDataService dataService, IDbTransaction transaction, IEnumerable dataObjects); + void AfterSuccessUpdateObjects(Guid operationId, IDataService dataService, IEnumerable dataObjects); + void AfterCommitUpdateObjects(Guid operationId, IDataService dataService, IEnumerable dataObjects); + void AfterFailUpdateObjects(Guid operationId, IDataService dataService, IEnumerable dataObjects); + void CleanupStateStore(Guid operationId); +} +``` + +## Жизненный цикл обновления + +### Успешное обновление + +```text +BeforeUpdateObjects + └── Начало обработки, сбор изменений + +SQL-запросы (INSERT/UPDATE/DELETE) + └── Выполнение запросов к базе данных + +AfterSuccessSqlUpdateObjects + └── Уведомление после SQL, но до фиксации + +AfterSuccessUpdateObjects + └── Уведомление после обработки + +COMMIT TRANSACTION + └── фиксация изменений в базе + +AfterCommitUpdateObjects + └── уведомление после гарантии сохранения + +CleanupStateStore + └── Очистка памяти +``` + +### Обновление с ошибкой + +```text +BeforeUpdateObjects + └── Начало обработки + +SQL-запросы (INSERT/UPDATE/DELETE) + └── Выполнение запросов к базе данных + +ROLLBACK TRANSACTION + └── откат транзакции + +AfterFailUpdateObjects + └── Уведомление о провале + +CleanupStateStore + └── Очистка памяти (предотвращение утечек) +``` + +## Сценарии использования + +### Отправка уведомлений + +После успешного коммита можно безопасно отправлять email, SMS или push-уведомления: + +```csharp +public class Order : DataObject, INotifyUpdateObject +{ + public string Email { get; set; } + public decimal Amount { get; set; } + + public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) + { + var order = (Order)dataObject; + + if (status == ObjectStatus.Created || status == ObjectStatus.Altered) + { + var emailService = DependencyResolver.Current.GetService(); + emailService.SendOrderConfirmation(order.Email, order.Amount); + } + } +} +``` + +### Интеграция с внешними системами + +Вызов внешнего API после гарантированной записи в базу: + +```csharp +public class Product : DataObject, INotifyUpdateObject +{ + public string ExternalId { get; set; } + public string Name { get; set; } + + public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) + { + var product = (Product)dataObject; + + var syncService = DependencyResolver.Current.GetService(); + + switch (status) + { + case ObjectStatus.Created: + syncService.CreateProduct(product.Id, product.Name); + break; + case ObjectStatus.Altered: + syncService.UpdateProduct(product.Id, product.Name); + break; + case ObjectStatus.Deleted: + syncService.DeleteProduct(product.Id); + break; + } + } +} +``` + +### Сброс кэша + +Инвалидация кэша только после фиксации изменений: + +```csharp +public class Customer : DataObject, INotifyUpdateObject +{ + public string Name { get; set; } + + public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) + { + var cache = DependencyResolver.Current.GetService(); + cache.Remove($"customer_{dataObject.__PrimaryKey}"); + } +} +``` + +### Уровень свойств + +Для свойств с типом, реализующим `INotifyUpdateProperty`: + +```csharp +public class ComplexProperty : INotifyUpdateProperty +{ + public string Value { get; set; } + + public void AfterCommitUpdateProperty( + DataObject dataObject, + ObjectStatus status, + string propertyName, + object oldValue, + object newValue) + { + var complexProp = (ComplexProperty)newValue; + Logger.Info($"Property {propertyName} changed to {complexProp.Value}"); + } +} +``` + +## Очистка памяти + +Метод `CleanupStateStore` вызывается при ошибках для удаления внутреннего состояния операции. Это предотвращает утечки памяти: + +```csharp +public virtual void CleanupStateStore(Guid operationId) +{ + stateStore.Remove(operationId); +} +``` + +## Централизованный менеджер уведомлений + +Менеджер уведомлений настраивается через `NotifierUpdateObjects`: + +```csharp +var dataService = new SQLDataService(); +dataService.NotifierUpdateObjects = new NotifierUpdateObjects(); +``` + +Для обработки свойств можно использовать пользовательскую реализацию: + +```csharp +dataService.NotifierUpdateObjects = new NotifierUpdateObjects( + new CustomPropertyNotifier()); +``` + +## Обработка ошибок + +В post-commit методах исключения не выбрасывают — транзакция уже зафиксирована. Обработка ошибок выполняется через try/catch: + +```csharp +public void AfterCommitUpdateObject(DataObject dataObject, ObjectStatus status, IEnumerable dataObjects) +{ + try + { + SendEmail(...); + } + catch (Exception ex) + { + logger.Error("Failed to send email after commit", ex); + } +} +``` + +## Статусы объектов + +Состояние объекта передаётся через `ObjectStatus`: + +- **Created** — объект создан +- **Altered** — объект изменён +- **Deleted** — объект удалён + +Различие операций возможно по значению статуса: + +```csharp +if (status == ObjectStatus.Deleted) +{ + // Обработка удаления +} +``` + +## Принципы работы + +### Внутреннее хранилище состояний + +Внутреннее хранилище (`stateStore`) хранит состояние изменений для каждой операции: + +- По `operationId` — идентификатор операции +- По `Type` — тип объекта +- По `PrimaryKey` — первичный ключ объекта +- По `PropertyName` — имя свойства + +Каждое хранимое значение — `Tuple`: статус, старое и новое значение. + +### Очерёдность вызовов + +1. **BeforeUpdateObjects** — сбор изменений до SQL-запросов +2. **AfterSuccessSqlUpdateObjects** — после выполнения SQL, до COMMIT +3. **AfterSuccessUpdateObjects** — после обработки, до COMMIT +4. **AfterCommitUpdateObjects** — после гарантированной фиксации +5. **AfterFailUpdateObjects** — при откате транзакции + +## Уведомления в системе + +Система уведомлений интегрирована в `SQLDataService` и автоматически вызывает соответствующие методы на всех этапах жизненного цикла обновления. Ключевые компоненты: + +- **NotifierUpdateObjects** — стандартная реализация `INotifyUpdateObjects` +- **DbTransactionWrapper** — оборачивает транзакцию, отслеживает идентификаторы операций +- **StateStore** — внутреннее хранилище состояний + +## Рекомендации + +Поведение методов уведомлений регулируется следующими принципами: + +- **AfterCommitUpdateObjects** — для действий, требующих гарантии сохранения (email, API, логи) + +- **AfterSuccessUpdateObjects** — для операций без требования коммита (внутренняя валидация, кэширование на уровне приложения) + +- Длительные операции в post-commit методах избегают + +- Ошибки в post-commit обрабатывают через try/catch; исключения не выбрасывают — транзакция уже зафиксирована + +- Уведомления могут приходить для удалённых объектов; статус проверяют через `ObjectStatus`