Создание полноценного .NET-приложения с Lino: пошаговое руководство

В этом разделе представлено практическое пошаговое руководство по использованию Lino CLI в качестве основного инструмента при создании проекта: от установки и начальной настройки через создание сервисов, модулей и сущностей до расширенных функций, таких как события, Background Jobs, миграции, создание образов Docker и управление версиями.

Цель состоит в том, чтобы комплексно показать, как команды CLI вписываются в реальный процесс разработки — не просто перечислить их, но объяснить, почему делается каждый выбор, что генерируется автоматически и каковы архитектурные последствия.

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

На протяжении всего руководства мы будем объяснять технические концепции, применяемые Lino (например: CQRS, TypedResults, Source Generators, Outbox Pattern), мы покажем примеры команд и укажем передовые методы управления версиями, развертывания и непрерывной интеграции.

Используйте эту дорожную карту как последовательность обучения: сначала поймите структуру, затем смоделируйте домен, только затем создавайте API, страницы, события и артефакты развертывания. Это уменьшает количество доработок и обеспечивает соответствие сгенерированного кода дизайну системы.

Установка и настройка Lino CLI

Первый шаг к началу работы с Lino CLI заключается в установке инструмента в вашей среде разработки. Он распространяется как глобальный инструмент dotnet, что означает, что он будет доступен для любого проекта.NET на вашем компьютере. Перед установкой убедитесь, что.NET SDK, необходимый для текущих шаблонов, доступен и что терминал может работать. dotnet и что глобальный каталог инструментов.NET находится в папке PATH.

Шаг 1: Установка

Чтобы установить (или обновить) Lino CLI, выполните в терминале следующую команду:

dotnet tool install --global Tolitech.Lino

Важные примечания:

  • Если версия уже установлена, вы можете использовать dotnet tool update --global Tolitech.Lino обновить.
  • Убедитесь, что глобальный каталог инструментов.NET находится в папке PATH системы так, чтобы команда lino функционировать правильно.

Шаг 2. Настройте язык

После установки рекомендуется настроить язык (или культуру), который CLI будет использовать в сообщениях, подсказках и журналах:

lino preferences culture set

Вам будет предложено выбрать один из доступных языков. Этот параметр гарантирует, что все инструкции и подсказки будут отображаться на нужном языке одинаково. Это предпочтение изменяет локализованные сообщения, подсказки и инструкции CLI; он не переименовывает сущности, услуги, модули или бизнес-термины, созданные в проекте.

Шаг 3: Аутентификация и регистрация

Чтобы получить доступ ко всем функциям Lino, включая расширенные шаблоны, публикацию изображений Docker и интеграцию с внешними службами, вам необходимо пройти аутентификацию.

- Если вы еще не зарегистрированы, зарегистрируйтесь командой:

lino user register

- Если вы уже зарегистрированы, войдите с помощью:

lino auth login

Что происходит: CLI хранит токен аутентификации локально, что позволяет вам выполнять команды, требующие доступа к защищенным ресурсам, без необходимости входа в систему каждый раз, когда вы его используете. Сохраняйте этот токен конфиденциальным и избегайте совместного использования одного и того же профиля пользователя разными разработчиками, агентами CI или компьютерами.

Шаг 4: Проверка

Чтобы убедиться, что установка и аутентификация прошли успешно, запустите:

lino --version

Если команда вернет установленную версию, вы готовы начать использовать Lino CLI в ваших проектах.

Создание проекта MyApp

На этом этапе мы создадим первоначальную структуру проекта, используя Lino CLI. Этот проект послужит основой для демонстрации создания сервисов, модулей, интерфейса и всей интеграции событий. Проект Lino — это не просто папка с решением: он определяет соглашения о границах сервисов, общих библиотеках, хосте Aspire, платформе внешнего интерфейса, тестах, управлении пакетами, анализаторах и конфигурациях, которые повторно используются следующими командами.

Шаг 1. Запуск команды создания

Чтобы создать новый проект, выполните в терминале команду ниже:

lino project new

CLI будет вести вас шаг за шагом, запрашивая такую ​​информацию, как:

  • Название проекта: мы будем использовать MyApp, но вы можете выбрать любое имя;
  • Дополнительные возможности: анализаторы кода, распределенное кэширование, поддержка асинхронных событий и т. д.

Шаг 2. Настройка основных функций

Для этого проекта мы рекомендуем с самого начала включить следующие функции:

  • Анализаторы кода: гарантировать, что код соответствует передовой практике и последовательным стандартам, предотвращая распространенные ошибки реализации;
  • Распределенный кэш: повышает производительность приложений в сценариях с несколькими сервисами, избегая ненужных запросов к базе данных;
  • Асинхронная связь: позволяет использовать события и очереди для интеграции между сервисами, обеспечивая масштабируемость и развязку.

Важно включить все эти параметры в этом проекте, поскольку мы создадим несколько сервисов, которые будут взаимодействовать посредством событий интеграции. Это позволит вам понять, как структурировать модульные и распределенные системы с помощью Lino.

Шаг 3: Сгенерированная структура

После выполнения команды и настройки ресурсов CLI сгенерирует исходную структуру проекта. Он будет включать:

  • Папки служб и модулей;
  • Шаблоны внешнего интерфейса (если применимо);
  • Начальные настройки кэша, событий и интеграций;
  • Файлы решения (.slnx) и проекта (.csproj), готовые к компиляции.

Теперь ваш проект Мое приложение готов получить сервисы, модули, сущности и интерфейс, которые мы настроим на следующих шагах. Прежде чем продолжить, откройте сгенерированное решение и запустите dotnet build чтобы убедиться, что ссылки, генераторы исходного кода, шаблоны, файлы проекта и первоначальная конфигурация согласованы.

Добавление веб-приложения Backoffice

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

Веб-приложение не заменяет доменные службы. Он действует как визуальная точка входа для использования API, запуска команд, просмотра запросов и отображения экранов в соответствии с правилами, уже смоделированными в сервисах.

Шаг 1. Запуск команды создания

Чтобы добавить в проект новое веб-приложение, используйте:

lino web-app new

Alias lino webapp new также доступен для более удобного ввода. Во время выполнения укажите понятное имя веб-приложения; в этом примере мы используем Backoffice.

lino web-app new --name Backoffice

Шаг 2. Понимание созданной структуры

В конце процесса Lino создает начальную структуру Web App в src/WebApps/<WebAppName>. Для приложения Blazor структура может включать server/client проекты, общие ресурсы, файлы локализации, clients для потребления APIs и соглашения, которые позже будут использоваться lino page new.

  • Папки для страниц, компонентов, макетов, сервисов и ресурсов приложения;
  • Клиенты и контракты HTTP, необходимые для использования API, предоставляемых службами проекта;
  • Ресурсы локализации и исходные шаблоны, используемые в веб-интерфейсе;
  • Клиент-серверные проекты, когда тип приложения требует такого разделения;
  • Соглашения о маршрутах, навигации и интеграции, которые будут повторно использоваться при создании страниц;
  • Точки интеграции с аутентификацией и авторизацией при добавлении функции аутентификации в проект.

Шаг 3. Когда создавать веб-приложение в потоке

Если вы уже знаете, что у системы будет интерфейс Blazor, создайте Web App в самом начале, сразу после создания проекта. Так services, modules, entities, APIs и pages, созданные позже, уже будут согласованы с веб-приложением, которое будет их потреблять.

Этот поток особенно полезен, потому что services, созданные позже, также генерируют типизированные проекты Api.Contracts и Api.Client, потребляемые проектом Blazor. Когда Web App присутствует с самого начала, проще проверить полный путь: domain, API, contracts, HttpClient и экран.

Важные примечания

  • Бэк-офис должен использовать данные через сгенерированные API, поддерживая бизнес-логику в правильных службах и модулях.
  • Прежде чем открывать доступ к административным страницам, просмотрите аутентификацию, авторизацию, роли, разрешения и политики доступа.
  • Вы можете создать несколько веб-приложений, например Backoffice внутренний и Site общедоступный, когда аудитории, разрешения, развертывания или обязанности различаются.
  • Избегайте смешивания общедоступных и административных потоков в одном веб-приложении, если это затрудняет безопасность, навигацию, развертывание или владение командой.

После создания веб-приложения руководство может перейти к сервисам и модулям, которые будут предоставлять данные и бизнес-правила, используемые этим интерфейсом.

Создание сервисов и модулей

На этом этапе мы создадим сервисы и модули, составляющие приложение. Цель состоит в том, чтобы создать модульную и масштабируемую архитектуру, позволяющую различным областям системы, таким как продукты, категории, запасы, продажи и средства массовой информации, развиваться независимо, сохраняя целостность и облегчая обслуживание. Службы определяют границы развертывания и устойчивости; Модули организуют бизнес-направления в рамках модульного сервиса. Прежде чем создавать файлы, оцените, какие части домена нуждаются в собственной базе данных, независимом выпуске, соглашении об интеграции или отдельном владении.

Шаг 1: Определение услуг

Первоначально мы создадим следующие службы, каждая из которых будет иметь четко определенные обязанности:

  • Catalog (модульный) – отвечает за управление товарами, категориями и ценами;
  • Sales – отвечает за обработку продаж и заказов;
  • Stock – отвечает за управление запасами и движением;
  • Security – отвечает за аутентификацию, авторизацию и управление пользователями.

Чтобы создать services из примера, выполните по одной команде для каждого service. В wizard для Catalog выберите modular architecture; для остальных выберите architecture, подходящую для простой границы.

lino service new --name Catalog
lino service new --name Sales
lino service new --name Stock
lino service new --name Security

Во время выполнения команды CLI запросит:

  • Имя service: например, Catalog;
  • Отображаемое имя и архитектурный стиль: выберите простую architecture для компактных границ или modular, когда service имеет независимые связные области;
  • База данных: выберите технологию, которая лучше всего подходит вашему проекту (SQL Server, PostgreSQL и т. д.);

Шаг 2: Создание modules внутри services

Не все services должны быть modular. В нашем project только service Catalog будет иметь modules, чтобы разделить ответственности, такие как merchandising и pricing.

Для service Catalog мы определяем следующие modules:

  • Merchandising – управление продуктами и категориями;
  • Pricing – управление ценами, акциями и историей изменений.

Чтобы создать modules для Catalog, выполните:

lino module new --service Catalog --name Merchandising
lino module new --service Catalog --name Pricing

В модульном сервисе вы можете создать столько модулей, сколько захотите. Catalog. Более того, в зависимости от сложности некоторые модули в будущем могут стать самостоятельными сервисами. Представленное здесь разделение предназначено только для дидактических целей и служит примером модульной организации. Не просто используйте модули для создания папок; используйте их, когда они защищают бизнес-сферу с ее собственными объектами, вариантами использования, API, миграциями и событиями.

Окончательная структура проекта

После создания сервисов и модулей ваше решение должно иметь структуру, подобную следующей:

MyApp/
└── src/
    ├── Aspire/
    ├── Integrations/
    ├── Services/
    │   ├── Catalog/
    │   │   ├── Modules/
    │   │   │   ├── Merchandising/
    │   │   │   └── Pricing/
    │   │   ├── MyApp.Catalog.Host
    │   │   └── MyApp.Catalog.Infrastructure
    │   ├── Sales/
    │   ├── Security/
    │   ├── Shared/
    │   └── Stock/
    └── WebApps/
        ├── Backoffice/
        │   ├── Services/
        │   │   ├── Catalog/
        │   │   ├── Sales/
        │   │   ├── Security/
        │   │   └── Stock/
        │   ├── MyApp.WebApp.Backoffice
        │   └── MyApp.WebApp.Backoffice.Client
        └── Shared/
            └── MyApp.WebApp.Shared
└── tests/
    └── Services/
        ├── Catalog/
        │   ├── Merchandising/
        │   └── Pricing/
        ├── Sales/
        ├── Security/
        ├── Shared/
        └── Stock/

Объяснение структуры:

  • Services/: содержит все системные сервисы, каждый из которых изолирован своей бизнес-логикой, инфраструктурой и хостингом;
  • Modules/: папки внутри модульных сервисов, позволяющие организовать определенные функции и поддерживать целостный код;
  • WebApps/: интерфейсы, связанные с системой, уже интегрированные с сервисами;
  • Shared/: библиотеки и ресурсы, совместно используемые службами и интерфейсами;
  • tests/: модульные и интеграционные тесты, организованные по сервисам и модулям.

Благодаря такой модульной структуре каждая команда или разработчик может работать независимо над различными частями системы, что облегчает масштабируемость, обслуживание и тестирование. После создания сервисов и модулей запустите dotnet build чтобы убедиться, что проекты правильно подключены к решению, хосту Aspire, общим проектам и платформе тестирования.

Добавляем аутентификацию и авторизацию

Аутентификация и авторизация являются важными элементами любой современной системы. Аутентификация доказывает, кем является пользователь; авторизация определяет, что может выполнять этот пользователь. В Lino CLI функция аутентификации генерирует необходимую базу для пользователей, ролей, разрешений, токенов, политик доступа и интеграции с APIs.

Шаг 1. Запуск команды аутентификации

Чтобы добавить функции аутентификации и авторизации, используйте команду:

lino feature auth add

CLI проведет вас через следующие шаги:

  • Выбор услуги или модуля: вам необходимо указать, куда будут установлены артефакты аутентификации. В нашем примере проекта мы используем сервис Security, который централизует всю логику безопасности системы;
  • Дополнительные настройки: создание таблиц пользователей, ролей, разрешений, токенов и настройка политик доступа;
  • Срок действия токена: определение срока действия токена доступа и токена обновления в соответствии с политикой безопасности продукта;
  • Тип идентификатора пользователя: выбор типа, используемого пользовательской моделью и генерируемыми контрактами.

Шаг 2: Сгенерированная структура

После выполнения команды служба Security будет содержать файлы и папки, такие как:

  • Домен/Объекты: агрегаты, сущности и правила для пользователей, ролей, разрешений и токенов;
  • Инфраструктура/постоянство: конфигурации базы данных, сопоставления Entity Framework и миграции для таблиц безопасности;
  • Приложение: Команды, запросы, обработчики, службы аутентификации, генерация токенов, проверка учетных данных и проверки разрешений;
  • API/Хост: конечные точки для входа в систему, выхода из системы, регистрации, обновления токена и защищенных операций;
  • Интеграция с веб-приложением: поддержка аутентифицированных потоков при наличии веб-приложения.

Благодаря этому ваше приложение будет иметь надежную аутентификацию и детальный контроль доступа, готовое поддерживать несколько пользователей и различные уровни разрешений. Тем не менее, сгенерированный код следует рассматривать как прочную основу, а не как окончательную проверку безопасности. Перед производством проверьте срок действия токена, структуру разрешений, политику паролей, HTTPS, секреты, ограничение скорости, журналы и конфигурации развертывания.

Добавление фоновых заданий

В распределенных и модульных системах, подобных той, которую мы строим. Lino CLI, не все службы взаимодействуют друг с другом напрямую. Чтобы обеспечить согласованность и надежность обмена информацией, мы используем интеграционные мероприятия. Однако для эффективной и асинхронной обработки этих событий нам необходимо Background Jobs.

Lino использует Outbox Pattern, чтобы гарантировать, что все сообщения, генерируемые службами, надежно записываются перед отправкой. Благодаря этому мы добились:

  • Избегайте потери событий в случае сбоев или перезапусков сервиса;
  • Убедитесь, что одно и то же сообщение отправляется только один раз;
  • Разрешить повторную обработку сообщений в случае сбоя доставки;
  • Отделите обработку событий от основной логики приложения, улучшая производительность и масштабируемость.

Шаг 1: Выполнение команды

Чтобы добавить поддержку фоновых заданий в ваш проект, выполните команду:

lino feature background-job add

CLI попросит вас выбрать службу, в которую будет установлено Background Job. Как правило, вы выбираете службу, которая централизует создание событий, например Catalog или Sales. В текущих опциях мастер также может запросить модуль, библиотеку заданий, необходимость обработки событий Outbox, расписание и размер пакета. Текущий поток шаблонов использует Hangfire для повторяющегося выполнения заданий.

Шаг 2. Настройка выполнения

В ходе настройки вы сможете определить:

  • Интервал проверки: определяет, как часто Background Job будет проверять таблицу Outbox для новых сообщений. Слишком короткий интервал может увеличить использование ресурсов, а слишком длинный интервал может задержать доставку событий;
  • Пакет записей, обрабатываемых одновременно: контролирует, сколько событий будет прочитано и отправлено за один запуск. Большие пакеты могут повысить производительность, но требуют больше памяти и обработки;
  • Политика поиска: В случае сбоя отправки сообщений вы можете настроить, сколько раз задание будет пытаться отправить сообщение повторно.

Эти параметры зависят от размера вашей системы, мощности машины и ожидаемого объема событий.

Шаг 3: Сгенерированная структура

После настройки в проекте появится Background Job, готовое к обработке сообщений из таблиц. Outbox в каждом сервисе.

  1. Вариант использования меняет домен и записывает домен или событие интеграции.
  2. Единица работы сохраняет бизнес-данные и сообщения из Outbox в одной транзакции.
  3. Hangfire запускает повторяющиеся задания, которые считывают ожидающие сообщения в пакетном режиме.
  4. Сообщения публикуются в настроенном механизме интеграции, например RabbitMQ, когда асинхронная связь включена.
  5. Завершенные, неудачные, старые или застрявшие сообщения могут обрабатываться с помощью логики и конфигурации, созданных для задания.

Это гарантирует, что все события интеграции обрабатываются надежно и эффективно, позволяя нескольким службам и модулям взаимодействовать асинхронно, не влияя на производительность основной системы. Самым важным операционным правилом является сохранение четкости границ транзакций: события, которые необходимо отправлять через Outbox, должны создаваться в том же транзакционном потоке, что и бизнес-изменение, которое они представляют.

Создание сущностей и перечислений

В этом разделе мы подробно опишем дизайн сущностей приложения, перечислений и объектов значений, показав, в каких сервисах и модулях будет создан каждый элемент.

1. Создание сущности Category

Чтобы создать entity Category в сервисе Catalog и модуле Merchandising, выполните:

lino entity new --service Catalog --module Merchandising --name Category

Сущность будет создана в сервисе Catalog и в модуле Merchandising со следующей структурой:

┌────┬────┬───────────────┬────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type   │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid   │        │    x     │       x        │
├────┼────┼───────────────┼────────┼────────┼──────────┼────────────────┤
│    │    │ Name          │ string │   50   │    x     │                │
└────┴────┴───────────────┴────────┴────────┴──────────┴────────────────┘

2. Создание сущности Product

Затем мы создаем entity Product в том же сервисе и модуле. В этом потоке Value Object ProductDimension и enum ProductStatus настраиваются внутри самого wizard создания entity как часть aggregate Product:

lino entity new --service Catalog --module Merchandising --name Product

Во время выполнения добавьте простые properties, relationship с Category, property Dimensions типа Value Object и property Status типа Enum.

┌────┬────┬───────────────┬─────────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type        │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid        │        │    x     │       x        │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│    │    │ Name          │ string      │  100   │    x     │                │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│    │    │ Description   │ string      │  500   │    x     │                │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│    │    │ Price         │ decimal     │        │    x     │                │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│    │ x  │ CategoryId    │ Category    │        │    x     │                │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│    │    │ Dimensions    │ ValueObject │        │          │                │
├────┼────┼───────────────┼─────────────┼────────┼──────────┼────────────────┤
│    │    │ Status        │ Enum        │        │    x     │                │
└────┴────┴───────────────┴─────────────┴────────┴──────────┴────────────────┘

2.1 Настройка Value Object ProductDimension внутри Product

В сценарии этого руководства ProductDimension не создается отдельной командой. Он добавляется во время lino entity new для Product как property Dimensions и представляет размеры продукта:

┌───────────────┬─────────┬────────┬──────────┐
│ Property name │ Type    │ Length │ Required │
├───────────────┼─────────┼────────┼──────────┤
│ Width         │ decimal │        │    x     │
├───────────────┼─────────┼────────┼──────────┤
│ Height        │ decimal │        │    x     │
├───────────────┼─────────┼────────┼──────────┤
│ Depth         │ decimal │        │    x     │
└───────────────┴─────────┴────────┴──────────┘

Примечание: команда lino value-object new существует для сценариев, где Value Object нужно создать отдельно. В таких случаях используйте аргументы целевого service и module:

lino value-object new --service <ServiceName> --module <ModuleName> --name <ValueObjectName>

2.2 Настройка Enum ProductStatus внутри Product

Аналогично, ProductStatus настраивается внутри lino entity new для Product как property Status. Она определяет статус продукта:

┌───────┬──────────────┬──────────────┐
│ Value │ Name         │ Display Name │
├───────┼──────────────┼──────────────┤
│ 1     │ Active       │ Active       │
├───────┼──────────────┼──────────────┤
│ 2     │ Inactive     │ Inactive     │
├───────┼──────────────┼──────────────┤
│ 3     │ Discontinued │ Discontinued │
└───────┴──────────────┴──────────────┘

Примечание: команда lino enumeration new также может использоваться, когда enum нужно создать отдельно в другом сценарии:

lino enumeration new --service <ServiceName> --module <ModuleName> --name <EnumerationName>

3. Добавление новых свойств

По мере развития проекта мы можем редактировать существующие объекты, добавляя новые свойства. Например, мы добавим в сущность список изображений Product:

lino entity edit --service Catalog --module Merchandising --entity Product

В этом же потоке мы создаем property Images типа List<ProductImage>. Поскольку ProductImage принадлежит aggregate Product, его структура также настраивается во время lino entity edit для Product:

┌────┬────┬───────────────┬────────────────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type               │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid               │        │    x     │       x        │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │    │ Name          │ string             │  100   │    x     │                │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │    │ Description   │ string             │  500   │    x     │                │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │    │ Price         │ decimal            │        │    x     │                │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │ x  │ CategoryId    │ EntityId           │        │    x     │                │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │    │ Dimensions    │ ValueObject        │        │          │                │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │    │ Status        │ Enum               │        │    x     │                │
├────┼────┼───────────────┼────────────────────┼────────┼──────────┼────────────────┤
│    │ x  │ Images        │ List │        │          │                │
└────┴────┴───────────────┴────────────────────┴────────┴──────────┴────────────────┘

3.1 Настройка ProductImage внутри Product

В этом сценарии ProductImage не создается отдельной командой. Он настраивается как элемент коллекции Images во время редактирования Product и имеет следующую структуру:

┌────┬────┬───────────────┬────────────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type           │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid           │        │    x     │       x        │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│    │ x  │ ProductId     │ EntityId       │        │    x     │                │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│    │    │ UploadDate    │ DateTimeOffset │        │    x     │                │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│    │    │ Image         │ File           │        │    x     │                │
└────┴────┴───────────────┴────────────────┴────────┴──────────┴────────────────┘

Примечание: команда lino entity new существует для создания независимых entities в других сценариях. Когда entity не является частью редактирования существующего aggregate, используйте:

lino entity new --service <ServiceName> --module <ModuleName> --name <EntityName>

4. Создание сущностей для других сервисов

В эксплуатации Sales, мы создаем сущность ProductSnapshot, который будет подпитываться событиями интеграции. В качестве исходного идентификатора объекта Product приходит со службы Catalog, здесь оно не может быть автоинкрементным.

lino entity new --service Sales --name ProductSnapshot
┌────┬────┬───────────────┬─────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type    │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼─────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid    │        │    x     │                │
├────┼────┼───────────────┼─────────┼────────┼──────────┼────────────────┤
│    │    │ Name          │ string  │  100   │    x     │                │
├────┼────┼───────────────┼─────────┼────────┼──────────┼────────────────┤
│    │    │ Price         │ decimal │        │    x     │                │
└────┴────┴───────────────┴─────────┴────────┴──────────┴────────────────┘

Примечание: в ProductSnapshot для сервиса Sales были скопированы только основные fields. Дополнительные entities, такие как Customer, Order и StockItem, здесь не детализируются, чтобы упростить документацию. ProductSnapshot работает как shadow entity: локальная, минимальная и контролируемая копия данных, владелец которых находится в другом сервисе. Это позволяет Sales получать необходимые данные продукта без прямой зависимости от entity или базы данных Catalog.

Такой тип структуры также можно создать командой lino shadow new, alias для lino shadow-entity new. В этом потоке Lino копирует структуру entity из другого service или module и позволяет выбрать только те properties, которые имеют смысл для потребляющего context.

lino shadow new --service <ServiceName> --module <ModuleName> --name <ShadowEntityName>

В примере исходная entity — Catalog.Merchandising.Product, а назначение — сервис Sales, с сохранением только fields, необходимых для продаж.

Создание событий и их обработчиков

Напомним, в предыдущей теме мы создали модульный сервис Catalog.Merchandising сущности Product, Category и ProductImage, находясь на дежурстве Sales мы создаем сущность ProductSnapshot.

Теперь давайте создадим события предметной области и события интеграции. Цель состоит в том, чтобы при создании или обновлении продуктов на сервисе Catalog, эти изменения реплицируются в потребительские службы, такие как Sales и Stock.

1. Создание доменных событий

Первый шаг — создать события предметной области. ProductCreated и ProductUpdated с командой:

lino event new

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

┌──────────────────────────────────────────────────────┬────────────────────────────────┐
│ Question                                             │ Answer                         │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select a service:                                    │ Catalog                        │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select a module:                                     │ Merchandising                  │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select a entity:                                     │ Product                        │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select the event type:                               │ Domain Event                   │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Enter the name of the event:                         │ ProductCreated                 │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Do you want to create an associated event handler?   │ Yes                            │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Trigger a integration event?                         │ Yes                            │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Choose the integration event to be triggered:        │ (Create new integration event) │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Enter the name of the event:                         │ ProductCreated                 │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Which model will be used for this integration event? │ Creation model                 │
└──────────────────────────────────────────────────────┴────────────────────────────────┘

Таким же образом создаем событие ProductUpdated:

┌──────────────────────────────────────────────────────┬────────────────────────────────┐
│ Question                                             │ Answer                         │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select a service:                                    │ Catalog                        │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select a module:                                     │ Merchandising                  │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select a entity:                                     │ Product                        │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Select the event type:                               │ Domain Event                   │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Enter the name of the event:                         │ ProductUpdated                 │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Do you want to create an associated event handler?   │ Yes                            │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Trigger a integration event?                         │ Yes                            │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Choose the integration event to be triggered:        │ (Create new integration event) │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Enter the name of the event:                         │ ProductUpdated                 │
├──────────────────────────────────────────────────────┼────────────────────────────────┤
│ Which model will be used for this integration event? │ Update model                   │
└──────────────────────────────────────────────────────┴────────────────────────────────┘

При этом мы имеем:

2. Создание обработчиков событий интеграции

Следующий шаг — определить, какие службы будут использовать события интеграции. Для этого мы используем:

lino event-handler new

Процесс создания включает в себя:

Например, в сервисе Sales мы создали обработчики для ProductCreated и ProductUpdated который будет использовать события, вызванные Catalog.Merchandising.Product:

┌────────────────────────────────────────────┬───────────────────┐
│ Question                                   │ Answer            │
├────────────────────────────────────────────┼───────────────────┤
│ Select a service:                          │ Sales             │
├────────────────────────────────────────────┼───────────────────┤
│ Select a entity:                           │ ProductSnapshot   │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event type:                     │ Integration Event │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event's service to be consumed: │ Catalog           │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event's module to be consumed:  │ Merchandising     │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event's entity to be consumed:  │ Product           │
├────────────────────────────────────────────┼───────────────────┤
│ Choose the event to be consumed:           │ ProductCreated    │
├────────────────────────────────────────────┼───────────────────┤
│ Enter the name of the event handler:       │ ProductCreated    │
└────────────────────────────────────────────┴───────────────────┘
┌────────────────────────────────────────────┬───────────────────┐
│ Question                                   │ Answer            │
├────────────────────────────────────────────┼───────────────────┤
│ Select a service:                          │ Sales             │
├────────────────────────────────────────────┼───────────────────┤
│ Select a entity:                           │ ProductSnapshot   │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event type:                     │ Integration Event │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event's service to be consumed: │ Catalog           │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event's module to be consumed:  │ Merchandising     │
├────────────────────────────────────────────┼───────────────────┤
│ Select the event's entity to be consumed:  │ Product           │
├────────────────────────────────────────────┼───────────────────┤
│ Choose the event to be consumed:           │ ProductUpdated    │
├────────────────────────────────────────────┼───────────────────┤
│ Enter the name of the event handler:       │ ProductUpdated    │
└────────────────────────────────────────────┴───────────────────┘

При этом у нас есть два обработчика событий интеграции в сервисе. Sales, потребляя только необходимые поля из событий интеграции Catalog.Merchandising. Это гарантирует, что реплицированные таблицы сохранят только необходимые данные, оптимизируя хранение и производительность. С точки зрения архитектуры ожидаемый поток таков: обработчик команд изменяет агрегат, вызывается событие домена, обработчик готовит событие интеграции, Outbox сохраняет сообщение, Background Job обрабатывает проблему, а механизм обмена сообщениями доставляет сообщение потребителям. Это позволяет избежать синхронной связи между службами и снижает риск изменения базы данных без публикации соответствующего события.

Создание веб-страниц, API, команд и запросов.

Одно из больших преимуществ Lino CLI заключается в том, чтобы обеспечить интегрированное создание веб-страниц, API, команд и запросов в автоматическом режиме, упрощая весь процесс разработки. Когда модель предметной области становится достаточно стабильной, эта команда создает полный путь от экрана к сохранению, делая работу по моделированию, выполненную на предыдущих шагах, видимой для конечного пользователя. Для начала просто выполните команду:

lino page new

В ходе процесса вы:

С помощью этой команды вы получаете функциональное приложение без необходимости вручную писать уровень интерфейса, API и бизнес-логику, сохраняя стандарт и согласованность между сервисами. Тем не менее, просмотрите бизнес-правила и проверки после создания, поскольку не каждое правило можно вывести только на основе метаданных свойств.

Для этого проекта мы можем создать интегрированные страницы для следующих объектов:

После создания страниц, API и команд/запросов приложение будет готово к полному взаимодействию между интерфейсом и сервером, с проверками, маршрутами и постоянством, уже настроенными автоматически Lino CLI. Бегать dotnet build после генерации, чтобы заранее выявить неработающие ссылки, противоречивые контракты или влияние недавних изменений модели.

Создание и применение миграций

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

Lino CLI координирует этот процесс с Entity Framework, выбирая правильный сервис/модуль, используя текущую версию сервиса и организуя сгенерированные сценарии для облегчения отслеживания.

Шаг 1. Создание миграции

Чтобы создать новую миграцию, запустите:

lino database migrations add

Команда также может принимать псевдонимы, например lino database migrations new и lino database migrations create, но предпочтительный способ в документации: add.

При исполнении необходимо сообщить:

┌───────────────────────────────────────────┬───────────────────┐
│ Question                                  │ Answer            │
├───────────────────────────────────────────┼───────────────────┤
│ Select a service:                         │ Catalog           │
├───────────────────────────────────────────┼───────────────────┤
│ Select a module:                          │ Merchandising     │
├───────────────────────────────────────────┼───────────────────┤
│ Current version of the service:           │ 0.1.0             │
├───────────────────────────────────────────┼───────────────────┤
│ Provide a description for this migration: │ Initial migration │
└───────────────────────────────────────────┴───────────────────┘

Когда вы уже знаете сервис и модуль, вы можете ввести эти данные напрямую:

lino database migrations add --service <ServiceName> --module <ModuleName>

Шаг 2: Что генерируется

При подтверждении создания Lino подготавливает правильные команды Entity Framework для выбранного проекта сохранения службы или модуля. В модульном сервисе миграция остается изолированной в соответствующем модуле, что позволяет избежать смешивания изменений из разных контекстов.

Шаг 3. Листинг и применение миграций

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

lino database migrations list --service <ServiceName> --module <ModuleName>

Чтобы применить миграцию к настроенной среде:

lino database migrations apply --service <ServiceName> --module <ModuleName>

При локальной разработке этот процесс ускоряет проверку модели. В общих или производственных средах примените миграцию в рамках процесса развертывания, определенного командой, с проверкой сценария, утверждением и резервным копированием при необходимости.

Шаг 4. Отмена или удаление в контролируемой среде

Использовать revert когда вам нужно вернуть миграцию, примененную в контролируемой среде, понимая, что операция может выполнять деструктивные команды в зависимости от содержимого миграции:

lino database migrations revert --service <ServiceName> --module <ModuleName>

Использовать remove чтобы отменить последнюю незафиксированную миграцию, обычно перед фиксацией или публикацией изменения:

lino database migrations remove --service <ServiceName> --module <ModuleName>

Передовая практика

Следуя этому потоку, база данных остается согласованной с моделью предметной области, определенной в Lino, и каждое изменение схемы документируется, отслеживается и готово к проверке перед развертыванием.

Шаг 5: Локальная проверка приложения

Когда project, Web App, services, modules, entities, migrations, APIs, Commands и Queries готовы, проверьте приложение перед генерацией Docker images. Сначала соберите solution, чтобы убедиться, что все сгенерированные projects, contracts и clients остаются согласованными:

dotnet build

Затем запустите приложение через Aspire AppHost:

dotnet run --project src/Aspire/AppHost/<ProjectName>.AppHost.csproj

Используйте dashboard Aspire, чтобы проверить, что APIs, Web App, database, cache, messaging и Background Jobs запустились корректно. Затем протестируйте основные flows в Backoffice: сгенерированные pages, вызовы Blazor к проектам Api.Client, примененные migrations, authentication, если она включена, а также events или jobs, если они входят в сценарий.

Когда приложение собирается, запускается локально и основные flows проверены, project готов к этапу packaging.

Генерация изображений Docker

После того как приложение собирается, запускается локально через AppHost и основные flows протестированы, можно сгенерировать Docker images для services и web applications для последующей публикации в container registry. Используйте lino build, когда выбранные элементы готовы к упаковке как images.

Lino CLI упрощает этот процесс с помощью команды:

lino build

При запуске вы увидите список всех сервисов и веб-приложений, доступных в проекте, а также их текущие версии:

Select the services or web applications you want to include in the build:

> [ ] Services
    [ ] Catalog |0.1.0|
    [ ] Sales |0.1.0|
    [ ] Security |0.1.0|
    [ ] Stock |0.1.0|
  [ ] Web applications
    [ ] Backoffice |0.1.0|

Вы можете выбрать одну или несколько служб и веб-приложений для одновременного создания изображений. Просто отметьте нужные позиции.

Затем вам будет предложено выбрать, как вы хотите обновить версию сгенерированных изображений. Доступные варианты:

После выбора служб и определения приращения версии Lino CLI выполняет:

В конце процесса, учитывая, что все веб-сервисы и приложения выбраны, сгенерированные изображения будут иметь следующую структуру:

Вообще говоря, простые сервисы имеют тенденцию создавать репозитории, такие как project-name/services/service-name-api:1.2.3модульные сервисы используют хосты в качестве project-name/services/service-name-host:1.2.3, а приложения Blazor используют такие пути, как project-name/webapps/webapp-name:1.2.3.

Наблюдение: Этот процесс гарантирует согласованность кода и версии образов Docker, облегчая развертывание и обслуживание нескольких сред, а также позволяя изолировать каждую службу в независимых контейнерах. После создания локального образа опубликуйте его в реестре, используемом вашей платформой развертывания, например Docker Hub, GitHub Container Registry, AWS ECR, Azure Container Registry или другом реестре, совместимом с OCI. Не включайте в образ секреты, строки производственного подключения или учетные данные.

Создание версий в приложении

Lino поддерживает рабочую версию каждого сервиса в src/Services/<ServiceName>/version.txt и каждое веб-приложение в src/WebApps/<WebAppName>/version.txt. Это позволяет планировать независимые выпуски для каждого развертываемого элемента.

Прежде чем менять версии, проверьте текущее состояние:

lino version list

Использовать lino version show когда вам нужно обратиться к конкретному сервису или веб-приложению.

Увеличение новых версий веб-сервисов или приложений — это простой и централизованный процесс. Lino CLI. Просто запустите команду:

lino version bump

Как и при создании изображений Docker, при запуске этой команды вы увидите полный список всех сервисов и веб-приложений в вашем проекте. Версия только выбранных вами предметов будет увеличена, остальные останутся неизменными.

Select the services or web applications that will have version changes:

> [ ] Services
    [ ] Catalog |0.1.0|
    [ ] Sales |0.1.0|
    [ ] Security |0.1.0|
    [ ] Stock |0.1.0|
  [ ] Web applications
    [ ] Backoffice |0.1.0|

После выбора нужных пунктов вам будет предложено выбрать тип приращения версии. Доступные варианты:

Важно подчеркнуть, что версии веб-сервисов и приложений напрямую влияют на:

Прежде чем применять обновление, просмотрите изменения кода, миграции, события интеграции, контракты API и изменения внешнего интерфейса, которые являются частью выпуска. Одна версия Пластырь должны представлять совместимые исправления, Незначительный должны представлять собой совместимые дополнения, и Главный его следует зарезервировать для изменений, которые требуют от потребителей адаптации.

На этом мы завершаем пошаговое руководство по всем основным командам для создания веб-проекта с использованием Lino CLI, от установки, создания служб, сущностей, событий и страниц до создания образов Docker и управления версиями. Весь процесс отслеживается: моделирование предметной области, создание сценариев использования и экранов, проверка с помощью сборок и тестов, создание миграций, публикация изображений с тегами версий и выпуск каждой службы или веб-приложения с явным значением SemVer.

Не забудьте подписаться на наш канал Ютуб следовать подробным руководствам, практическим демонстрациям и советам по использованию инструмента, от простых операций до расширенных функций.

Произошла необработанная ошибка. Обновить 🗙