Есть ли жизнь с ORM или
типовая архитектура
CRUD приложения
Велижанин Николай
Архитектура – самые важные решения
Типичное приложение с
типичными проблемами
Приложение
• Single Page App (SPA) with backend
• ~80% операций – Create, Read, Update, Delete (CRUD)
• Отчеты
• Database Driven Design
• Простая предметная логика
• Запросы в виде SQL внедрены в код
Проблемы
• Синхронизация схемы БД и кода приложения
• Что поменялось в БД?
• Изменения в схеме БД – какие SQL запросы это затронуло?
• Динамические структуры данных DataTable, Dictionary
• Схем объектов в коде нет. Определяются результатами SQL запросов
• Типы полей зависят от СУБД decimal если Oracle, double если MS SQL Server
• Поддержка нескольких СУБД
• Архитектурные решения не соблюдаются некоторыми
разработчиками
• Сложное развертывание / обновление
Может быть по-другому?
«Шаблон» приложения
• Успешно применен в 3 проектах
• Количество запросов на SQL стремится к нулю
За исключением пары-тройкиView и десятка хранимок
• Контроль соответствия кода и БД
• Переносимость блоков
Админка, справочники, отчёты, …
• Модифицируемость «шаблона» под текущие нужды без оглядки
на другие проекты
Если ORM, то какая?
Micro-ORMs: Dapper, PetaPoco
• Решают часть проблем
• Нет LINQ
• Нет генерации SQL
Heavy: Entity Framework
• Lazy Loading
• Миграции
• ChangeTracking
• Ассоциации Eager loading даже для коллекций
• LINQ
• Code first, Database first
Linq2DB
• Ассоциации Eager loading только для 1:1
• LINQ
• Bulk operations
• Оконные функции (Аналитические функции)
• Update и Delete с предикатом
• Custom’ные SQL функции и выражения в LINQ запросе
• Настраиваемые скриптыT4 для генерации модели на C#
• Расширяемость
• Отзывчивое сообщество на GitHub, быстро принимают pull request’ы
В чём сила, LINQ
• Запросы более предметные (ассоциации вместо join’ов)
• Запросы проверяются компилятором C#
• IntelliSense
• Легко производить рефакторинг, в том числе БД
• Проецирование
from q in query
from ls in db.PrRrlslayersortings.Where(ls => q.LclstrhistoryId == ls.LclstrhistoryId
&& q.Lsformation.LslayerId == ls.LslayerId).DefaultIfEmpty()
let f = new FormationInfo
{
КонсолидированноеГоловноеПредприятие = q.Lclstrhistory.Ddocompany.Parent.Shortname,
Предприятие = q.Lclstrhistory.Ddocompany.Shortname,
ПочтовыйАдрес = q.Lclstrhistory.Ddocompany.Mailingaddress,
ОКПО = q.Lclstrhistory.Ddocompany.Okpo,
ОКТМО = q.Lclstrhistory.Ddocompany.Oktmo,
ИНН = q.Lclstrhistory.Ddocompany.Inn,
КПП = q.Lclstrhistory.Ddocompany.Kpp,
…
НомерЛицензииИДатаРегистрации = q.License == null
? "лицензия не указана"
: $"{q.License.Series.Shortname}{q.License.No}{q.License.Kind.Shortname} от
{q.License.Begindate.ToShortDateString()}",
ЛицензионныйБлок = q.License == null
? "лицензия не указана"
: $"{q.License.Blockname} {q.License.Series.Shortname} {q.License.No}
{q.License.Kind.Shortname} {q.License.Begindate.ToShortDateString()}",
Лицензия = q.License == null
? "лицензия не указана“
: $"{q.License.Series.Shortname}{q.License.No}{q.License.Kind.Shortname}",
ГлубинаЗалеганияМин = q.Gasmindepth ?? q.Oilmindepth,
ГлубинаЗалеганияМакс = q.Gasmaxdepth ?? q.Oilmaxdepth,
// LEFT JOIN
// Ассоциации
// Базовый запрос
Предметная модель
Анемичная предметная модель
• Генерируется с помощьюT4 по существующей БД
• Практически не содержит логики. Допускается в partial классах
• Всегда соответствует актуальной схеме
• Тонкий типизированный слой абстракции над БД
• После перегенерации нерабочий код не собирается. Знаем что
править
Типичная сущность
Прикладной уровень
• В виде WEB API контроллеров
• Процедурный стиль
• ~95% - это запросы на LINQ
• Защищает предметную модель
Типичный подход: сущность – клиенту
• Провоцирует загрязнение предметной
модели внешними проблемами: UI,
сериализация.
• Поля «сбоку» для нужд одной формы
• Чувствительная или системная информация
• Лишние для конкретных сценариев поля
• Поменяли схему сущности – а в какихView
она используется? Где исправлять?
Модель
WEB API
Клиент (JavaScript)
Company
Запрос /
Company
DataTransfer Object (DTO)
• Для каждого сценария – свой DTO
• Содержит только требуемые поля
сущности
• Какие угодно дополнительные поля
• Может автоматически преобразовываться
в/из сущности. Включая ассоциации
• Может составляться из нескольких
сущностей
• Применение DTO обязательно для всего
WEB API
Модель
WEB API
Клиент (JavaScript)
Company
CompanyDTO
Запрос /
CompanyDTO
Запрос /
Company
Пример DTO
LocalStructLayer DTO
• Id
• Name
• Dome
• BeginStockTakingYear
• EndStockTakingYear
• …
• StratigraphicStageIds
• StratigraphicSystemName
• StratigraphicSystemSortOrder
• StratigraphicSeryName
• StratigraphicSerySortOrder
• …
LocalStructLayer
StratigraphicSeries
StratigraphicSystem
StratigraphicStage
1
:
1
1…1
• Id
• Name
• Dome
• BeginStockTakingYear
• EndStockTakingYear
• …
• Name
• SortOrder
• …
• Name
• SortOrder
• …
• Id
• Name
• SortOrder
• …
Система / Отдел / Ярус
Пласт локальных структур
Как гарантировать
обязательность DTO?
Задача: чтобы сделать неправильно было
невозможно
Обязательность DTO
• Проверка на уровне json сериализатора
AutoMapper как преобразователь DTO
Company
Company DTOAutoMapper
Configuration
Fluent Attribute based
LINQ Query Проецирование
AutoMapper
• [MapFromEntity]
• Convention-based matching или [MapFromProperty]
• Unit test для проверки, что все свойства в DTO успешно
преобразуются. config.AssertConfigurationIsValid()
Отчётность
Отчёты
• Разрабатываются с помощью LINQ
• Именование классов кириллицей
• На каждый отчёт обязательный интеграционный тест
• Наличие интеграционного теста проверяется Unit test’ом
• Интеграционный тест – xUnit тест, но выполняются ночью или
под отладчиком.Требуется БД.
Выполнение отчёта
• Отчёт может формироваться какое-то время (1-2 минуты)
• Формируем через персистентную очередь
• Для простого приложения, разрабатываемого одной командой,
достаточно Hangfire. Поддерживает MS SQL Server, Redis
Конфигурация
Типичный подход к конфигурации
• Настройки в web.config, app.config
• Разработчик иногда меняет web/app.config
• assemblyBinding, DbProviderFactories, logging, …
• При обновлении на сервере приходится мержить (пропускать,
перезаписывать?) app/web.config
• В тяжелых случаях конфиги разбросаны по разным местам
Конфигурация. Наш подход
• Web/app.config = бинарники
• Все, что настраивается, в отдельном файле. Лучше в одном.
• В одном месте: App_Data/Cfg, bin/../Cfg
Где используется?
• OIS Запасы и ресурсы: Новая классификация запасов
• OIS ППР РБ: ИС Поддержки и принятия решений по развитию
ресурсной базы
• OISТехнолог
• OIS Ремонты
Спасибо!
• Вопросы?

Есть ли жизнь с ORM или типовая архитектура CRUD приложения

  • 1.
    Есть ли жизньс ORM или типовая архитектура CRUD приложения Велижанин Николай
  • 2.
    Архитектура – самыеважные решения
  • 3.
  • 4.
    Приложение • Single PageApp (SPA) with backend • ~80% операций – Create, Read, Update, Delete (CRUD) • Отчеты • Database Driven Design • Простая предметная логика • Запросы в виде SQL внедрены в код
  • 5.
    Проблемы • Синхронизация схемыБД и кода приложения • Что поменялось в БД? • Изменения в схеме БД – какие SQL запросы это затронуло? • Динамические структуры данных DataTable, Dictionary • Схем объектов в коде нет. Определяются результатами SQL запросов • Типы полей зависят от СУБД decimal если Oracle, double если MS SQL Server • Поддержка нескольких СУБД • Архитектурные решения не соблюдаются некоторыми разработчиками • Сложное развертывание / обновление
  • 6.
  • 7.
    «Шаблон» приложения • Успешноприменен в 3 проектах • Количество запросов на SQL стремится к нулю За исключением пары-тройкиView и десятка хранимок • Контроль соответствия кода и БД • Переносимость блоков Админка, справочники, отчёты, … • Модифицируемость «шаблона» под текущие нужды без оглядки на другие проекты
  • 8.
    Если ORM, токакая?
  • 9.
    Micro-ORMs: Dapper, PetaPoco •Решают часть проблем • Нет LINQ • Нет генерации SQL
  • 10.
    Heavy: Entity Framework •Lazy Loading • Миграции • ChangeTracking • Ассоциации Eager loading даже для коллекций • LINQ • Code first, Database first
  • 11.
    Linq2DB • Ассоциации Eagerloading только для 1:1 • LINQ • Bulk operations • Оконные функции (Аналитические функции) • Update и Delete с предикатом • Custom’ные SQL функции и выражения в LINQ запросе • Настраиваемые скриптыT4 для генерации модели на C# • Расширяемость • Отзывчивое сообщество на GitHub, быстро принимают pull request’ы
  • 12.
    В чём сила,LINQ • Запросы более предметные (ассоциации вместо join’ов) • Запросы проверяются компилятором C# • IntelliSense • Легко производить рефакторинг, в том числе БД • Проецирование
  • 13.
    from q inquery from ls in db.PrRrlslayersortings.Where(ls => q.LclstrhistoryId == ls.LclstrhistoryId && q.Lsformation.LslayerId == ls.LslayerId).DefaultIfEmpty() let f = new FormationInfo { КонсолидированноеГоловноеПредприятие = q.Lclstrhistory.Ddocompany.Parent.Shortname, Предприятие = q.Lclstrhistory.Ddocompany.Shortname, ПочтовыйАдрес = q.Lclstrhistory.Ddocompany.Mailingaddress, ОКПО = q.Lclstrhistory.Ddocompany.Okpo, ОКТМО = q.Lclstrhistory.Ddocompany.Oktmo, ИНН = q.Lclstrhistory.Ddocompany.Inn, КПП = q.Lclstrhistory.Ddocompany.Kpp, … НомерЛицензииИДатаРегистрации = q.License == null ? "лицензия не указана" : $"{q.License.Series.Shortname}{q.License.No}{q.License.Kind.Shortname} от {q.License.Begindate.ToShortDateString()}", ЛицензионныйБлок = q.License == null ? "лицензия не указана" : $"{q.License.Blockname} {q.License.Series.Shortname} {q.License.No} {q.License.Kind.Shortname} {q.License.Begindate.ToShortDateString()}", Лицензия = q.License == null ? "лицензия не указана“ : $"{q.License.Series.Shortname}{q.License.No}{q.License.Kind.Shortname}", ГлубинаЗалеганияМин = q.Gasmindepth ?? q.Oilmindepth, ГлубинаЗалеганияМакс = q.Gasmaxdepth ?? q.Oilmaxdepth, // LEFT JOIN // Ассоциации // Базовый запрос
  • 15.
  • 16.
    Анемичная предметная модель •Генерируется с помощьюT4 по существующей БД • Практически не содержит логики. Допускается в partial классах • Всегда соответствует актуальной схеме • Тонкий типизированный слой абстракции над БД • После перегенерации нерабочий код не собирается. Знаем что править
  • 17.
  • 18.
    Прикладной уровень • Ввиде WEB API контроллеров • Процедурный стиль • ~95% - это запросы на LINQ • Защищает предметную модель
  • 19.
    Типичный подход: сущность– клиенту • Провоцирует загрязнение предметной модели внешними проблемами: UI, сериализация. • Поля «сбоку» для нужд одной формы • Чувствительная или системная информация • Лишние для конкретных сценариев поля • Поменяли схему сущности – а в какихView она используется? Где исправлять? Модель WEB API Клиент (JavaScript) Company Запрос / Company
  • 20.
    DataTransfer Object (DTO) •Для каждого сценария – свой DTO • Содержит только требуемые поля сущности • Какие угодно дополнительные поля • Может автоматически преобразовываться в/из сущности. Включая ассоциации • Может составляться из нескольких сущностей • Применение DTO обязательно для всего WEB API Модель WEB API Клиент (JavaScript) Company CompanyDTO Запрос / CompanyDTO Запрос / Company
  • 21.
  • 22.
    LocalStructLayer DTO • Id •Name • Dome • BeginStockTakingYear • EndStockTakingYear • … • StratigraphicStageIds • StratigraphicSystemName • StratigraphicSystemSortOrder • StratigraphicSeryName • StratigraphicSerySortOrder • … LocalStructLayer StratigraphicSeries StratigraphicSystem StratigraphicStage 1 : 1 1…1 • Id • Name • Dome • BeginStockTakingYear • EndStockTakingYear • … • Name • SortOrder • … • Name • SortOrder • … • Id • Name • SortOrder • … Система / Отдел / Ярус Пласт локальных структур
  • 23.
  • 24.
    Задача: чтобы сделатьнеправильно было невозможно
  • 25.
    Обязательность DTO • Проверкана уровне json сериализатора
  • 26.
    AutoMapper как преобразовательDTO Company Company DTOAutoMapper Configuration Fluent Attribute based LINQ Query Проецирование
  • 27.
    AutoMapper • [MapFromEntity] • Convention-basedmatching или [MapFromProperty] • Unit test для проверки, что все свойства в DTO успешно преобразуются. config.AssertConfigurationIsValid()
  • 28.
  • 29.
    Отчёты • Разрабатываются спомощью LINQ • Именование классов кириллицей • На каждый отчёт обязательный интеграционный тест • Наличие интеграционного теста проверяется Unit test’ом • Интеграционный тест – xUnit тест, но выполняются ночью или под отладчиком.Требуется БД.
  • 31.
    Выполнение отчёта • Отчётможет формироваться какое-то время (1-2 минуты) • Формируем через персистентную очередь • Для простого приложения, разрабатываемого одной командой, достаточно Hangfire. Поддерживает MS SQL Server, Redis
  • 32.
  • 33.
    Типичный подход кконфигурации • Настройки в web.config, app.config • Разработчик иногда меняет web/app.config • assemblyBinding, DbProviderFactories, logging, … • При обновлении на сервере приходится мержить (пропускать, перезаписывать?) app/web.config • В тяжелых случаях конфиги разбросаны по разным местам
  • 34.
    Конфигурация. Наш подход •Web/app.config = бинарники • Все, что настраивается, в отдельном файле. Лучше в одном. • В одном месте: App_Data/Cfg, bin/../Cfg
  • 35.
    Где используется? • OISЗапасы и ресурсы: Новая классификация запасов • OIS ППР РБ: ИС Поддержки и принятия решений по развитию ресурсной базы • OISТехнолог • OIS Ремонты
  • 36.

Editor's Notes

  • #12 Linq2DB создавался автором для решения своих производственных задач, EF создавался для абстрактных клиентов.