cs-element
Version:
Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support
749 lines (649 loc) • 25 kB
Markdown
# 🔄 State Machine и управление состояниями
CSElement включает мощную систему State Machine для управления состояниями элементов с поддержкой переходов, условий и действий.
## 📋 Содержание
- [Основы State Machine](#основы-state-machine)
- [Создание состояний и переходов](#создание-состояний-и-переходов)
- [События и действия](#события-и-действия)
- [Условия и охранники](#условия-и-охранники)
- [Полный пример](#полный-пример-система-заказов)
## Основы State Machine
### Создание State Machine
```typescript
import {
StateMachine,
StateMachineConfig,
StateMachineEventType,
State,
Transition
} from 'cs-element';
// Конфигурация State Machine
const config: StateMachineConfig = {
name: 'OrderStateMachine',
initialState: 'draft',
enableLogging: true,
enableProfiling: true,
states: [
{
name: 'draft',
description: 'Черновик заказа',
type: 'normal',
onEnter: async (context) => {
console.log('📝 Заказ переведен в черновик');
await context.element.setData('status', 'draft');
},
onExit: async (context) => {
console.log('📝 Выход из состояния черновика');
}
},
{
name: 'pending',
description: 'Ожидает подтверждения',
type: 'normal',
onEnter: async (context) => {
console.log('⏳ Заказ ожидает подтверждения');
await context.element.setData('status', 'pending');
await context.element.setData('submittedAt', new Date().toISOString());
}
},
{
name: 'confirmed',
description: 'Подтвержден',
type: 'normal',
onEnter: async (context) => {
console.log('✅ Заказ подтвержден');
await context.element.setData('status', 'confirmed');
await context.element.setData('confirmedAt', new Date().toISOString());
}
},
{
name: 'cancelled',
description: 'Отменен',
type: 'final',
onEnter: async (context) => {
console.log('❌ Заказ отменен');
await context.element.setData('status', 'cancelled');
await context.element.setData('cancelledAt', new Date().toISOString());
}
}
],
transitions: [
{
id: 'submit',
from: 'draft',
to: 'pending',
event: 'SUBMIT',
description: 'Отправка заказа на подтверждение',
priority: 1
},
{
id: 'confirm',
from: 'pending',
to: 'confirmed',
event: 'CONFIRM',
description: 'Подтверждение заказа',
priority: 1
},
{
id: 'cancel-draft',
from: 'draft',
to: 'cancelled',
event: 'CANCEL',
description: 'Отмена черновика',
priority: 2
},
{
id: 'cancel-pending',
from: 'pending',
to: 'cancelled',
event: 'CANCEL',
description: 'Отмена ожидающего заказа',
priority: 2
}
]
};
// Создание элемента и State Machine
const orderElement = new CSElement('order-123');
const stateMachine = new StateMachine(orderElement, config);
```
### Отправка событий
```typescript
// Отправка заказа
const submitResult = await stateMachine.send('SUBMIT');
console.log('Результат отправки:', submitResult);
// Подтверждение заказа
const confirmResult = await stateMachine.send('CONFIRM');
console.log('Результат подтверждения:', confirmResult);
// Отмена заказа
const cancelResult = await stateMachine.send('CANCEL');
console.log('Результат отмены:', cancelResult);
// Получение текущего состояния
console.log('Текущее состояние:', stateMachine.currentState);
// Получение возможных переходов
const possibleTransitions = stateMachine.getPossibleTransitions();
console.log('Возможные переходы:', possibleTransitions.map(t => t.event));
```
## Создание состояний и переходов
### Динамическое создание состояний
```typescript
class OrderStateMachineBuilder {
private states: State[] = [];
private transitions: Transition[] = [];
addState(name: string, config: {
description?: string;
type?: 'normal' | 'initial' | 'final';
onEnter?: (context: any) => Promise<void>;
onExit?: (context: any) => Promise<void>;
}): this {
this.states.push({
name,
description: config.description || name,
type: config.type || 'normal',
onEnter: config.onEnter,
onExit: config.onExit
});
return this;
}
addTransition(from: string, to: string, event: string, config: {
description?: string;
priority?: number;
condition?: (context: any, event: any) => Promise<boolean>;
action?: (context: any, event: any) => Promise<void>;
} = {}): this {
this.transitions.push({
id: `${from}-${to}-${event}`,
from,
to,
event,
description: config.description || `${from} → ${to}`,
priority: config.priority || 1,
condition: config.condition,
action: config.action
});
return this;
}
build(element: CSElement, name: string, initialState: string): StateMachine {
const config: StateMachineConfig = {
name,
initialState,
enableLogging: true,
states: this.states,
transitions: this.transitions
};
return new StateMachine(element, config);
}
}
// Использование builder'а
const orderSM = new OrderStateMachineBuilder()
.addState('draft', {
description: 'Черновик заказа',
onEnter: async (context) => {
await context.element.setData('status', 'draft');
}
})
.addState('processing', {
description: 'Обработка заказа',
onEnter: async (context) => {
await context.element.setData('status', 'processing');
await context.element.setData('processingStarted', new Date().toISOString());
}
})
.addState('completed', {
description: 'Заказ выполнен',
type: 'final',
onEnter: async (context) => {
await context.element.setData('status', 'completed');
await context.element.setData('completedAt', new Date().toISOString());
}
})
.addTransition('draft', 'processing', 'START_PROCESSING', {
description: 'Начать обработку заказа',
condition: async (context) => {
const items = await context.element.getData('items') as any[];
return items && items.length > 0;
},
action: async (context) => {
console.log('🚀 Начинаем обработку заказа');
await context.element.setData('processingBy', 'system');
}
})
.addTransition('processing', 'completed', 'COMPLETE', {
description: 'Завершить заказ',
action: async (context) => {
console.log('✅ Заказ завершен');
await context.element.setData('completedBy', 'system');
}
})
.build(orderElement, 'OrderProcessor', 'draft');
```
## События и действия
### Обработка событий
```typescript
// Подписка на события State Machine
stateMachine.subscribe(StateMachineEventType.TRANSITION_STARTED, (data) => {
console.log(`🔄 Начался переход: ${data.fromState} → ${data.toState}`);
});
stateMachine.subscribe(StateMachineEventType.TRANSITION_COMPLETED, (data) => {
console.log(`✅ Переход завершен: ${data.fromState} → ${data.toState} (${data.duration}ms)`);
});
stateMachine.subscribe(StateMachineEventType.TRANSITION_FAILED, (data) => {
console.error(`❌ Переход неудачен: ${data.fromState} → ${data.toState}`, data.error);
});
stateMachine.subscribe(StateMachineEventType.STATE_ENTERED, (data) => {
console.log(`📍 Вошли в состояние: ${data.state}`);
});
stateMachine.subscribe(StateMachineEventType.STATE_EXITED, (data) => {
console.log(`📤 Покинули состояние: ${data.state}`);
});
```
### Сложные действия
```typescript
// Переход с асинхронными действиями
const complexTransition: Transition = {
id: 'process-payment',
from: 'pending',
to: 'paid',
event: 'PROCESS_PAYMENT',
description: 'Обработка платежа',
condition: async (context, event) => {
const amount = await context.element.getData('amount') as number;
const paymentMethod = event.payload?.paymentMethod;
return amount > 0 && paymentMethod;
},
action: async (context, event) => {
const amount = await context.element.getData('amount') as number;
const paymentMethod = event.payload.paymentMethod;
// Имитация обработки платежа
console.log(`💳 Обработка платежа ${amount} через ${paymentMethod}`);
// Сохраняем детали платежа
await context.element.setData('paymentMethod', paymentMethod);
await context.element.setData('paymentProcessedAt', new Date().toISOString());
// Имитация внешнего API
await new Promise(resolve => setTimeout(resolve, 1000));
if (Math.random() > 0.1) { // 90% успеха
await context.element.setData('paymentStatus', 'success');
console.log('✅ Платеж успешно обработан');
} else {
await context.element.setData('paymentStatus', 'failed');
throw new Error('Ошибка обработки платежа');
}
}
};
// Отправка события с данными
const paymentResult = await stateMachine.send('PROCESS_PAYMENT', {
paymentMethod: 'credit_card',
cardNumber: '**** **** **** 1234'
});
```
## Условия и охранники
### Условные переходы
```typescript
// Переход с проверкой условий
const conditionalTransition: Transition = {
id: 'approve-order',
from: 'pending',
to: 'approved',
event: 'APPROVE',
description: 'Одобрение заказа',
condition: async (context, event) => {
const amount = await context.element.getData('amount') as number;
const userRole = event.payload?.userRole;
// Менеджеры могут одобрять заказы до 10000
if (userRole === 'manager' && amount <= 10000) {
return true;
}
// Директора могут одобрять любые заказы
if (userRole === 'director') {
return true;
}
return false;
},
action: async (context, event) => {
const approver = event.payload.userRole;
await context.element.setData('approvedBy', approver);
await context.element.setData('approvedAt', new Date().toISOString());
console.log(`✅ Заказ одобрен пользователем с ролью: ${approver}`);
}
};
// Использование условного перехода
const approvalResult = await stateMachine.send('APPROVE', {
userRole: 'manager',
userId: 'user-123'
});
```
### Сложные условия
```typescript
class OrderValidator {
static async validateOrderItems(context: any): Promise<boolean> {
const items = await context.element.getData('items') as any[];
if (!items || items.length === 0) {
return false;
}
// Проверяем каждый товар
for (const item of items) {
if (!item.id || !item.quantity || item.quantity <= 0) {
return false;
}
// Проверяем наличие на складе
const inStock = await this.checkInventory(item.id, item.quantity);
if (!inStock) {
return false;
}
}
return true;
}
static async validateCustomer(context: any): Promise<boolean> {
const customerId = await context.element.getData('customerId') as string;
if (!customerId) {
return false;
}
// Проверяем статус клиента
const customerStatus = await this.getCustomerStatus(customerId);
return customerStatus === 'active';
}
static async validatePayment(context: any): Promise<boolean> {
const amount = await context.element.getData('amount') as number;
const paymentMethod = await context.element.getData('paymentMethod') as string;
if (!amount || amount <= 0) {
return false;
}
if (!paymentMethod) {
return false;
}
// Проверяем лимиты платежей
return amount <= 100000; // максимум 100,000
}
private static async checkInventory(itemId: string, quantity: number): Promise<boolean> {
// Имитация проверки склада
return Math.random() > 0.1; // 90% товаров в наличии
}
private static async getCustomerStatus(customerId: string): Promise<string> {
// Имитация проверки статуса клиента
return Math.random() > 0.05 ? 'active' : 'blocked';
}
}
// Использование валидатора в переходах
const validateTransition: Transition = {
id: 'validate-order',
from: 'draft',
to: 'validated',
event: 'VALIDATE',
description: 'Валидация заказа',
condition: async (context, event) => {
const itemsValid = await OrderValidator.validateOrderItems(context);
const customerValid = await OrderValidator.validateCustomer(context);
const paymentValid = await OrderValidator.validatePayment(context);
return itemsValid && customerValid && paymentValid;
},
action: async (context, event) => {
await context.element.setData('validatedAt', new Date().toISOString());
await context.element.setData('validatedBy', 'system');
console.log('✅ Заказ успешно валидирован');
}
};
```
## Полный пример: Система заказов
```typescript
class OrderManagementSystem {
private orders: Map<string, { element: CSElement; stateMachine: StateMachine }> = new Map();
async createOrder(orderData: {
customerId: string;
items: Array<{ id: string; quantity: number; price: number }>;
paymentMethod: string;
}): Promise<string> {
const orderId = `order-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// Создаем элемент заказа
const orderElement = new CSElement(orderId);
await orderElement.setData('customerId', orderData.customerId);
await orderElement.setData('items', orderData.items);
await orderElement.setData('paymentMethod', orderData.paymentMethod);
const totalAmount = orderData.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
await orderElement.setData('amount', totalAmount);
await orderElement.setData('createdAt', new Date().toISOString());
// Создаем State Machine
const stateMachine = this.createOrderStateMachine(orderElement);
// Сохраняем заказ
this.orders.set(orderId, { element: orderElement, stateMachine });
console.log(`🛒 Заказ ${orderId} создан на сумму ${totalAmount}`);
return orderId;
}
private createOrderStateMachine(element: CSElement): StateMachine {
const config: StateMachineConfig = {
name: 'OrderLifecycle',
initialState: 'draft',
enableLogging: true,
states: [
{
name: 'draft',
description: 'Черновик заказа',
type: 'initial',
onEnter: async (context) => {
await context.element.setData('status', 'draft');
}
},
{
name: 'validated',
description: 'Валидирован',
type: 'normal',
onEnter: async (context) => {
await context.element.setData('status', 'validated');
}
},
{
name: 'payment_processing',
description: 'Обработка платежа',
type: 'normal',
onEnter: async (context) => {
await context.element.setData('status', 'payment_processing');
}
},
{
name: 'paid',
description: 'Оплачен',
type: 'normal',
onEnter: async (context) => {
await context.element.setData('status', 'paid');
}
},
{
name: 'processing',
description: 'В обработке',
type: 'normal',
onEnter: async (context) => {
await context.element.setData('status', 'processing');
}
},
{
name: 'shipped',
description: 'Отправлен',
type: 'normal',
onEnter: async (context) => {
await context.element.setData('status', 'shipped');
}
},
{
name: 'delivered',
description: 'Доставлен',
type: 'final',
onEnter: async (context) => {
await context.element.setData('status', 'delivered');
await context.element.setData('deliveredAt', new Date().toISOString());
}
},
{
name: 'cancelled',
description: 'Отменен',
type: 'final',
onEnter: async (context) => {
await context.element.setData('status', 'cancelled');
await context.element.setData('cancelledAt', new Date().toISOString());
}
}
],
transitions: [
{
id: 'validate',
from: 'draft',
to: 'validated',
event: 'VALIDATE',
condition: async (context) => {
return await OrderValidator.validateOrderItems(context) &&
await OrderValidator.validateCustomer(context) &&
await OrderValidator.validatePayment(context);
}
},
{
id: 'process-payment',
from: 'validated',
to: 'payment_processing',
event: 'PROCESS_PAYMENT'
},
{
id: 'payment-success',
from: 'payment_processing',
to: 'paid',
event: 'PAYMENT_SUCCESS',
action: async (context) => {
await context.element.setData('paidAt', new Date().toISOString());
}
},
{
id: 'start-processing',
from: 'paid',
to: 'processing',
event: 'START_PROCESSING'
},
{
id: 'ship',
from: 'processing',
to: 'shipped',
event: 'SHIP',
action: async (context, event) => {
await context.element.setData('trackingNumber', event.payload?.trackingNumber);
await context.element.setData('shippedAt', new Date().toISOString());
}
},
{
id: 'deliver',
from: 'shipped',
to: 'delivered',
event: 'DELIVER'
},
{
id: 'cancel',
from: 'draft',
to: 'cancelled',
event: 'CANCEL'
},
{
id: 'cancel-validated',
from: 'validated',
to: 'cancelled',
event: 'CANCEL'
}
]
};
return new StateMachine(element, config);
}
async processOrder(orderId: string, event: string, payload?: any): Promise<any> {
const order = this.orders.get(orderId);
if (!order) {
throw new Error(`Заказ ${orderId} не найден`);
}
const result = await order.stateMachine.send(event, payload);
if (result.success) {
console.log(`✅ Событие ${event} для заказа ${orderId} обработано успешно`);
} else {
console.error(`❌ Ошибка обработки события ${event} для заказа ${orderId}:`, result.error);
}
return result;
}
async getOrderStatus(orderId: string): Promise<{
id: string;
status: string;
currentState: string;
possibleActions: string[];
history: any[];
}> {
const order = this.orders.get(orderId);
if (!order) {
throw new Error(`Заказ ${orderId} не найден`);
}
const status = await order.element.getData('status') as string;
const possibleTransitions = order.stateMachine.getPossibleTransitions();
const history = order.stateMachine.getHistory();
return {
id: orderId,
status,
currentState: order.stateMachine.currentState,
possibleActions: possibleTransitions.map(t => t.event),
history
};
}
async getAllOrders(): Promise<Array<{
id: string;
status: string;
amount: number;
createdAt: string;
}>> {
const orders = [];
for (const [orderId, order] of this.orders) {
const status = await order.element.getData('status') as string;
const amount = await order.element.getData('amount') as number;
const createdAt = await order.element.getData('createdAt') as string;
orders.push({
id: orderId,
status,
amount,
createdAt
});
}
return orders;
}
}
// Демонстрация использования
async function demonstrateOrderSystem() {
const orderSystem = new OrderManagementSystem();
try {
// Создаем заказ
const orderId = await orderSystem.createOrder({
customerId: 'customer-123',
items: [
{ id: 'item-1', quantity: 2, price: 100 },
{ id: 'item-2', quantity: 1, price: 200 }
],
paymentMethod: 'credit_card'
});
// Валидируем заказ
await orderSystem.processOrder(orderId, 'VALIDATE');
// Обрабатываем платеж
await orderSystem.processOrder(orderId, 'PROCESS_PAYMENT');
await orderSystem.processOrder(orderId, 'PAYMENT_SUCCESS');
// Начинаем обработку
await orderSystem.processOrder(orderId, 'START_PROCESSING');
// Отправляем заказ
await orderSystem.processOrder(orderId, 'SHIP', {
trackingNumber: 'TRACK123456'
});
// Доставляем заказ
await orderSystem.processOrder(orderId, 'DELIVER');
// Проверяем финальный статус
const finalStatus = await orderSystem.getOrderStatus(orderId);
console.log('📊 Финальный статус заказа:', finalStatus);
// Получаем все заказы
const allOrders = await orderSystem.getAllOrders();
console.log('📋 Все заказы:', allOrders);
} catch (error) {
console.error('❌ Ошибка в системе заказов:', error);
}
}
demonstrateOrderSystem().catch(console.error);
```
## 🎯 Заключение
State Machine в CSElement обеспечивает:
- **🔄 Четкое управление состояниями** - структурированные переходы между состояниями
- **🛡️ Валидация переходов** - условия и охранники для безопасных переходов
- **⚡ Асинхронные действия** - поддержка сложных операций при переходах
- **📊 Мониторинг** - отслеживание истории переходов и событий
- **🔧 Гибкость** - динамическое создание состояний и переходов
---
**Следующий раздел:** [Diff Engine и слияние данных →](12-diff-engine.md)