# Миграции

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

## Концепция миграций

Миграция — это скрипт, который автоматически выполняется при обновлении **плагина** и предназначен для решения следующих задач:

- Корректировка структуры таблиц данных (добавление полей, изменение типов полей и т. д.)
- Миграция данных (например, массовое обновление значений полей)
- Обновление конфигурации **плагина** или внутренней логики

Время выполнения миграций делится на три типа:

| Тип        | Время срабатывания                                      | Сценарий выполнения |
| ----------- | --------------------------------------------------- | ------------------ |
| `beforeLoad` | Перед загрузкой всех конфигураций **плагинов**         |                    |
| `afterSync`  | После синхронизации конфигураций **коллекций** с базой данных (структура **коллекции** уже изменена) | |
| `afterLoad`  | После загрузки всех конфигураций **плагинов**          |                    |

## Создание файлов миграции

Файлы миграции должны располагаться в каталоге **плагина** по пути `src/server/migrations/*.ts`. NocoBase предоставляет команду `create-migration` для быстрого создания файлов миграции.

```bash
yarn nocobase create-migration [options] <name>
```

Опциональные параметры

| Параметр      | Описание |
| -------------- | ----------- |
| `--pkg <pkg>`  | Укажите имя пакета **плагина** |
| `--on [on]`    | Укажите время выполнения, варианты: `beforeLoad`, `afterSync`, `afterLoad` |

Пример

```bash
$ yarn nocobase create-migration update-ui --pkg=@nocobase/plugin-client
```

Путь к сгенерированному файлу миграции:

```
/nocobase/packages/plugins/@nocobase/plugin-client/src/server/migrations/20240107173313-update-ui.ts
```

Начальное содержимое файла:

```ts
import { Migration } from '@nocobase/server';

export default class extends Migration {
  on = 'afterLoad'; // 'beforeLoad' | 'afterSync' | 'afterLoad'
  appVersion = '<0.19.0-alpha.3';

  async up() {
    // Напишите здесь логику обновления
  }
}
```

> ⚠️ `appVersion` используется для определения версии, к которой применяется обновление. Миграция будет выполнена в средах с версиями, меньшими указанной.

## Написание миграций

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

Общие свойства

- **`this.app`**  
  Текущий экземпляр приложения NocoBase. Может использоваться для доступа к глобальным сервисам, **плагинам** или конфигурации.  
  ```ts
  const config = this.app.config.get('database');
  ```

- **`this.db`**  
  Экземпляр сервиса базы данных, предоставляет интерфейсы для работы с моделями (**коллекциями**).  
  ```ts
  const users = await this.db.getRepository('users').findAll();
  ```

- **`this.plugin`**  
  Текущий экземпляр **плагина**, может использоваться для доступа к пользовательским методам **плагина**.  
  ```ts
  const settings = this.plugin.customMethod();
  ```

- **`this.sequelize`**  
  Экземпляр Sequelize, может напрямую выполнять необработанные SQL-запросы или транзакционные операции.  
  ```ts
  await this.sequelize.transaction(async (transaction) => {
    await this.sequelize.query('UPDATE users SET active = 1', { transaction });
  });
  ```

- **`this.queryInterface`**  
  QueryInterface Sequelize, часто используется для изменения структуры таблиц, например, для добавления полей, удаления таблиц и т. д.  
  ```ts
  await this.queryInterface.addColumn('users', 'age', {
    type: this.sequelize.Sequelize.INTEGER,
    allowNull: true,
  });
  ```

Пример написания миграции

```ts
import { Migration } from '@nocobase/server';

export default class extends Migration {
  on = 'afterSync';
  appVersion = '<0.19.0-alpha.3';

  async up() {
    // Используйте queryInterface для добавления поля
    await this.queryInterface.addColumn('users', 'nickname', {
      type: this.sequelize.Sequelize.STRING,
      allowNull: true,
    });

    // Используйте db для доступа к моделям данных
    const users = await this.db.getRepository('users').findAll();
    for (const user of users) {
      user.nickname = user.username;
      await user.save();
    }

    // Выполните пользовательский метод плагина
    await this.plugin.customMethod();
  }
}
```

Помимо перечисленных выше общих свойств, миграции также предоставляют богатый набор API. Подробную документацию см. в [API миграций](/api/server/migration).

## Запуск миграций

Выполнение миграций запускается командой `nocobase upgrade`:

```bash
$ yarn nocobase upgrade
```

При обновлении система определяет порядок выполнения на основе типа миграции и `appVersion`.

## Тестирование миграций

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

```ts
import { createMockServer, MockServer } from '@nocobase/test';

describe('Migration Test', () => {
  let app: MockServer;

  beforeEach(async () => {
    app = await createMockServer({
      plugins: ['my-plugin'], // Имя плагина
      version: '0.18.0-alpha.5', // Версия до обновления
    });
  });

  afterEach(async () => {
    await app.destroy();
  });

  test('run upgrade migration', async () => {
    await app.runCommand('upgrade');
    // Напишите логику проверки, например, существует ли поле, успешно ли перенесены данные
  });
});
```

> Совет: Использование Mock Server позволяет быстро имитировать сценарии обновления и проверять порядок выполнения миграций, а также изменения данных.

## Рекомендации по разработке

1.  **Разделяйте миграции**  
    Старайтесь генерировать по одному файлу миграции на каждое обновление, чтобы сохранить атомарность и упростить поиск и устранение неисправностей.
2.  **Указывайте время выполнения**  
    Выбирайте `beforeLoad`, `afterSync` или `afterLoad` в зависимости от объектов операции, чтобы избежать зависимости от незагруженных модулей.
3.  **Учитывайте управление версиями**  
    Используйте `appVersion` для четкого указания версии, к которой применяется миграция, чтобы предотвратить повторное выполнение.
4.  **Покрытие тестами**  
    Проверьте миграцию на Mock Server перед выполнением обновления в реальной среде.