Решили in-house разработать CRM систему. По ходу разработки встречались интересные моменты, которые постараюсь описать в нескольких статьях. В статьях постараюсь избежать банальностей типа: скачал, распаковал, запустил, и вот глядите, swagger из коробки. Таких статей, как и видео на Ютубах, уже очень много. Постараюсь поделиться просто интересными деталями, которые встречались по ходу разработки. Забегу вперед — систему настроили и запустили.

1. Почему разработка, а не покупка готовой системы

По двум причинам. Во вторых потому, что покупные системы со временем так обрастают донастройками, что от «коробки» там уже мало что остается. А во первых потому, что (та-дам!) фреймворки стремительно развились уже в какие то платформы, в которых кодить приходится мало.

2. Выбор фреймворка

Хотелось найти фреймворк, в котором уже есть весь Boiler код, который нужен для бизнес приложения: меню, разделы, графики, пользователи, и т. п. В ходе поисков такого фреймворка обратили внимание на .Net фреймворки https://aspnetboilerplate.com и https://abp.io, в которых уже «из коробки» много чего есть. Насколько я понял, оба фреймворка развивают то ли родственные команды, то ли вообще одна команда. Причем команды из Турции. Опять же та-дам! Вот что делается, когда в стране нет нефти (то ли )))), то ли (((( ). В фреймворке ASP.NET Boilerplate есть легаси код, от старых версий .Net Framework. В более новом фреймворке ABP легаси нет, он на .Net Core. У обоих фреймфорков прилично звезд на github.

Отмечу видео на Ютубе, Алексей Мерсин хорошо рассказывает по ASP.NET Boilerplate, и на 31:41 показывает симпатичный интерфейс, слева меню, есть цветная тема, в общем то, что нужно. Но возможно, я что-то не понял, но этот интерфейс, который демонстрирует Алексей, это платная версия фреймворка. Бесплатная версия «из коробки» выглядит уныло.

Далее попалась интересная библиотека для фронта - PrimeNG, у нее есть три ветки, под Angular, под React, под Vue. В каждой ветке есть магазин с темами оформления, есть платные темы, есть бесплатные темы. Выглядит все очень красиво, «из коробки» есть все, что нужно для фронта, меню, вкладки, кнопки, всплывающие уведомления. И опять же та-дам! За PrimeNG стоит опять же команда из Турции, PrimeTek.

В итоге решили разрабатывать на связке PrimeNG (Angular) + NestJs. Потому что фронт очень хочется на Angular, далее появляется желание экономить на экспертизе разработчиков, и поэтому пусть и на фронте и на бекэнд будет TypeScript.

Недостатки Node.js известны:
- TypeScript это все таки надстройка, отсутствие в JavaScript типов данных считаю недостатком.
- В папке node_modules будет лежать несколько десятков (сотен) тысяч файлов, написанных неизвестно кем.

2. Логирование запросов TypeORM

Для работы с базой данных фреймворк NestJs использует TypeORM. Библиотека TypeORM удивила, хорошо отрабатывает вносимые изменения в структуру таблицы, замену типов колонок, даже с данными в таблицах. А для того, чтобы посмотреть логи SQL запросов, которые порождаются TypeORM, нужно добавить параметр logging:

TypeOrmModule.forRoot({
type: 'mysql', // ...
entities: [ ContactEntity, ],
logging: true, // Для логирования SQL запросов в консоль
}),

3. Генерация UUID primary key в TypeORM

Boiler колонки в таблицах, которые должны быть в каждой таблице по умолчанию, это первичный ключ, дата создания, дата обновления. Если в TypeORM объявить:

export class ContactEntity {
  @ApiProperty({description: 'Первичный ключ'})
  @PrimaryGeneratedColumn()
  id: string;

@ApiProperty({description: 'Дата создания'})
  @CreateDateColumn()
created: Date;

@ApiProperty({description: 'Дата обновления'})
  @UpdateDateColumn()
  updated: Date;
}

То в MySQL базе это превратится в:

CREATE TABLE contacts (
id int(11) NOT NULL AUTO_INCREMENT,
created datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id)
);

Все хорошо. Но для бизнес приложения хочется первичный ключ UUID, а не целочисленный.

И если в TypeORM объявить:

export class ContactEntity {
  @ApiProperty({description: 'Первичный ключ'})
  @PrimaryGeneratedColumn('uuid')
  id: string;

 @ApiProperty({description: 'Дата создания'})
  @CreateDateColumn()
created: Date;

 @ApiProperty({description: 'Дата обновления'})
  @UpdateDateColumn()
  updated: Date;
}

То в SQL базе это превратится в:

CREATE TABLE contacts (
id varchar(36) NOT NULL,
created datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id)
);

То есть первичный ключ это просто строка, без автогенерации UUID значения! Сначала это показалось странно. Но, выяснилось, что в TypeORM осознанно сделано так, UUID генерируется в коде TypeORM и вставка записей идет с уже заполненным полем UUID ключа. Потому, что в случае автогенерируемой UUID колонки, при каких то видах вставки TypeORM приходилось бы затем считывать вставленные записи, и еще раз обновлять их. Это в итоге работало бы медленнее, чем генерировать UUID на стороне TypeORM.

4. Уведомления в главном меню

В главном меню, у названий разделов, можно вывести индикатор кол-ва записей в разделе. Например, на пункте меню «Заказы», нужно вывести индикатор заказов в статусе «Новый», чтобы сотрудник сразу обратил внимание на то, что в систему упали с сайта новые заказы, и нужно эти новые заказы быстрее отработать. Для этого в PrimeNG есть параметр badge.

В модуле AppMenuComponent, в модели меню и разделов, у пункта «Заказы» указываем badge и целочисленное значение:

this.model = [
  {
    label: 'Разделы',
      items:[
        {label: 'Контакты', icon: 'pi pi-fw pi-user', routerLink: ['/contact']},
        {label: 'Заказы', icon: 'pi pi-fw pi-table', routerLink: ['/order'], badge: 0},
      ]
  },
]

Обновлять значение придется по абсолютному адресу пункта меню в модели:

this.model[ 0 ].items[ 1 ].badge = countNewOrder;

Roadmap системы

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

b) Нужно прикрутить очередь заданий. Очевидно, что некоторые задачи могут выполняться неопределенное время, соответственно такие задачи нужно ставить в очередь, и потом уже забирать результаты.

c) Нужен workflow, например прохождения документа, хотя бы в элементарном виде, например, в виде справочника с этапами согласования документа.

d) Нужно прикрутить чаты и чат-бота.

Какие то куски того, что получается, будем показывать на демо стенде - PrimeNG demo.

Комментарии (2)


  1. siandreev
    19.07.2022 15:22

    Как по мне, статье не хватает более понятной структуры и высокоуровнего описания системы. Было бы интереснее узнать про строение клиентской и серверной стороны, возникшие сложности.


    1. camunar Автор
      19.07.2022 16:01

      Спасибо, опишу в следующей статье