# Сэмплы использования Evos API

Исполняемые примеры работы с публичным API Evos. Каждый сэмпл — самостоятельный сценарий,
который можно запустить и скопировать к себе.

Сэмплы одновременно служат smoke-тестами API: проект `Evos.Api.Samples.Tests` запускает каждый
из них как обычный тест, поэтому поломка публичного API падает на CI.

Описание самого API — в документации пакета: [низкоуровневое](../docs/LowLevelApi.md),
[высокоуровневое](../docs/HighLevelApi.md).

## Как запустить

```
dotnet run --project Evos.Api.Samples
```

Отработают все сэмплы по очереди; ненулевой код возврата означает, что какой-то сценарий сломался.

## Два API в одном пакете

Evos поставляет **низкоуровневое** и **высокоуровневое** API одним nuget-пакетом.

**Низкоуровневое** — прямая работа с репозиторием Evos: продукты, экземпляры, транзакции, версии.
Им пользуются, когда нужен полный контроль над моделью.

| Сэмпл | Какую работу показывает | Что проверяет |
|---|---|---|
| `RepositoryLifecycleSample` | Открыть репозиторий (`ApiConcurrentRepositoryFactory`), записать в транзакции, закрыть и открыть заново | Продукты переживают закрытие репозитория |
| `RepositoryEditSample` | Свойства фасадом: `Facade()` — чтение, `EditableFacade()` — запись; `SetUnlock` и переопределение в экземпляре; привязка экземпляра к уровню присоединяемым семейством | Значение записалось; переопределён ровно один экземпляр из двух; насос стоит на уровне |
| `VcsSample` | История версий без сервера: коммиты, ветки, переименование, откат | Первый коммит создаёт ветку; `Strip` убирает ровно один коммит |
| `RepeaterSample` | Повторитель: тиражирование геометрии сегмента вдоль оси с заданным шагом, подрезка последнего сегмента | У сегмента остаётся один повторитель; на оси встаёт нужное число сегментов; **ловушка**: шаг участвует только в создании — существующему повторителю новый шаг молча не применится |

**Высокоуровневое** — обменная модель (`ExchangeModel`): её собирают, сериализуют и публикуют
в Evos. Им пользуются, когда данные приходят из внешней системы.

| Сэмпл | Какую работу показывает | Что проверяет |
|---|---|---|
| `BuildExchangeModelSample` | Сборка модели: продукты, экземпляры с матрицами, система, линейный продукт, графика | Модель собирается; экземпляры не дубли благодаря `ModelObjectId` |
| `GraphicsSample` | Геометрия через `GraphicsBuilder`: переиспользование мешей, графика экземпляром, части графики | Одинаковый меш даёт один продукт; **ловушка**: цвет в хэш не входит; **ловушка**: `SetProductMesh` не регистрирует продукт для переиспользования; **ловушка**: меш становится ключевым свойством, то есть входит в тождество продукта |
| `CustomFamilySample` | Своё семейство: `FamilyDefinitionBase`, стабильный `[FamilyDefinitionId]`, свойство-перечисление | Семейство опубликовалось; enum уехал строкой из `[CustomEnumValue]` |
| `ValidationErrorsSample` | Разбор отказа `Build()` вместо `ValueOrThrow()`: `TryGetValue`, `Errors`, `ValidationErrorOutputPayload` | Четыре вида отказа: `DuplicateInstance`, `ProductWithModelIdHasKeyProperties`, `MissingProductKeyProperties`, `PropertyIsBothKeyAndNonKey` |
| `SerializeExchangeModelSample` | Сохранение модели в файл и в поток, чтение обратно | Идентификатор проекта и состав продуктов переживают round-trip |
| `PublishToRepositorySample` | **Связка двух API**: репозиторий открывают низкоуровневой фабрикой и отдают его же экспортёру. Публикация объектом, потоком и файлом; линейный продукт и графика экземпляром | Экспорт идемпотентен — в том числе для **заново собранной** модели со стабильными идентификаторами; линейный продукт публикуется повторителем |
| `SystemsAndLevelsSample` | Привязка экземпляра к системе, продукт-уровень (`EvosFamilies.Level`), категоризация в дерево каталога | Опубликованный воздуховод ссылается на продукт-систему |
| `CollaborationSample` | Проект на сервере: публикация объектом и потоком, чтение уровней и структуры, свой `IEvosReader`, свой `IUncommittedChangesProcessor` (что уедет на сервер — видно до коммита), оборудование на существующий уровень проекта (`ContainerId` + `LevelInstanceId`) | Не запускается из `Program` — нужен живой сервер |

## Как связаны два API

Отдельного «репозитория обменной модели» не существует. Репозиторий всегда открывает
низкоуровневая фабрика, и **тот же самый** `IConcurrentRepository` принимает высокоуровневый
экспортёр:

```csharp
using var repository = new ApiConcurrentRepositoryFactory().OpenNewRepository(location);

ExchangeModelToEvosExporterFactory.Create().Export(repository, model);
```

Открытие репозитория само по себе — `RepositoryLifecycleSample`. Связка «открыли низкоуровневым,
опубликовали высокоуровневым, прочитали результат обратно низкоуровневым» — `PublishToRepositorySample`.

## Что стоит знать заранее

**Ключевые свойства задают тождество.** Два продукта с одинаковым набором ключевых свойств —
это один и тот же продукт. Поэтому экземплярам продукта обязательно проставляют собственный
идентификатор (`GraphicFamilies.ModelObjectId.ObjectId`), иначе `Build()` сочтёт их дублями:
имя и матрица лежат в обычных свойствах и в тождество не входят. И самому продукту нужны
собственные ключи: служебный «ID проекта», который билдер ставит сам, за ключ не считается —
без своих ключей `Build()` вернёт `MissingProductKeyProperties`. Отказы `Build()` разобраны
в `ValidationErrorsSample`.

**Экспорт идемпотентен — если идентификаторы стабильны.** Повторная публикация той же модели
не плодит дубли, а обновляет существующие продукты (`PublishToRepositorySample`). Но экземпляры
Evos узнаёт по `projectId` модели и `ModelObjectId` экземпляра: сгенерируете их заново на каждый
запуск — получите дубли. Берите идентификаторы из своей системы.

**Публикация приводит проект в соответствие с моделью.** Экземпляры с тем же `projectId`, которых
в модели нет, из проекта удаляются, — публикуйте проект целиком, а не по частям.

**Запись и чтение не совмещаются.** Репозиторий не даёт взять write-блокировку поверх открытого
read-соединения. Прочитали нужное, закрыли соединение — потом пишите. См. `VcsSample`.

**Писать можно только в транзакции.** `TryExecuteTransaction` сам открывает write-соединение
и коммитит его на выходе.

## CollaborationSample

Единственный сэмпл, которому нужен живой Collaboration-сервер, поэтому он не запускается
из `Program`. Чтобы попробовать его, вызовите `CollaborationSample.Run(serverUrl, projectName, branchName)`,
подставив свои адрес и проект.

Проект и ветка должны существовать на сервере заранее — создавать их API не умеет.

Главный сценарий живёт здесь не по прихоти, а потому что иначе его не показать: чтобы поставить
оборудование на **существующий уровень проекта**, нужны два идентификатора — `ContainerId` (адрес
продукта-контейнера, в него кладут оборудование через `product.ModelId`) и `LevelInstanceId`
(ссылка на сам уровень, её кладут в `instance.Level`). Оба выдаёт только чтение проекта с сервера
(`GetLevels`), собрать их самому из локального репозитория публичным API нечем.

Адрес для прогона сэмпла тестами задаётся в `collaboration.settings.json` (или переменной
`EVOS_COLLABORATION_URL`); без него `CollaborationSampleTests` пропускается.
