---
tags: +AgentCRM
---
# Развитие архитектуры приложения для разработки CRM "+Агент"
Разработка CRM-системы требует развития существующей архитектуры, используемой в LxWallet. Необходимо решить ряд вопросов по Backend и Frontend архитектуре.
## Развитие Backend-архитектуры
### На будущее: валидация отдельных полей
Непроверенная идея: можно собирать DTO-объект со значениями, а потом устанавливать ему прототип нужной сущности и запускать вадидацию, пропуская undefined-поля (см. [документацию](https://github.com/typestack/class-validator#skipping-missing-properties)). Чтобы это работало, нельзя пользоваться аннотацией `@IsDefined`
### Асинхронная валидация с использованием репозиториев
Как реализоваить валидацию сущностей с использованием репозиториев (например, проверка на уникальность)? Проблема заключается в необходимости пробрасывать EntityManager. Пробрасывать его "руками" при каждой валидации плохо, так как можно запросто забыть (т. е., видимо, нужно создавать очередной protected-метод ConfigurableService `validateEntity`)
Пример проблемы: сейчас при создании услуги в UseCase проверяется на уникальность её код. Если бы создавался также UseCase обновления услуги, то код пришлось бы повторять. Соответственно, этот код валидации необходимо куда-то вынести. Причём нужно устроить всё так, чтобы при обновлении сущности нужно проверять код на уникальность ТОЛЬКО в случае его изменения, чтобы не делать лишних запросов к БД.
### Построение Views
Текущий вариант построения Views не очень удобен, нужно поразмыслить над его улучшением. Как вариант, вместо текущего вида:
```ts
const currentUserView: TEntityView<User> = {
id: true,
fullName: user => user.getFirstName() + ' ' user.getLastName()
}
```
использовать следующий вид:
```ts
const getCurrentUserView: TGetEntityView<User> = (user) => ({
id: user.getId(),
fullName: user.getFirstName() + ' ' user.getLastName()
})
```
При необходимости переиспользования View код выглядел бы так:
```ts
const getFullUserView: TGetEntityView<User> = (user) => {
return Object.assign(getBaseUserView(user), {
avatar: user.getAvatarPath()
})
}
```
### Транзакционные тесты и тесты с фикстурами
В текущем варианте тестов не один раз были ситуации, когда в метод создания репозитория/use-case я забывал пробросить транзакционный EntityManager, что вело к неожиданному результату теста. Необходимо сделать так, чтобы `this` функции теста имел методы для создания репозиториев и use-case'ов.
Альтернативный вариант - вместо EntityManager'а в метод теста пробрасывать объект, реализующий следующий интерфейс:
```ts
inteface ITestPersistanceController {
createRepo...
createUseCase...
createService...
}
```
Также, нужно создать функцию для describe с фикстурами (`describeWithFixtures (name: string, fixtures: AbstractFixture[], body: Function)`, чтобы не повторять код создания фикстур в beforeAll и код удаления в afterAll.
### Создание сущностей
Сейчас сущности создаются в статических методах. Это не принесло видимого удобства, поэтому, нужно задуматься, не лучше ли делать всё в классических конструкторах.
### Рефакторинг репозиториев
Нужно определиться с местом создания SortOptions - должны ли опции сортировки создаваться где-то, кроме репозиториев?
Также, думаю, нужно добавить в IEntityCollection метод `findIds` - для извлечения только `id` сущностей.
Кроме того, подумать, каким образом можно решить следующую проблему: при выполнении Join-операций в репозиториях используются имена атрибутов других сущностей; при изменении этих имен может произойти ошибка. Необходимо размещать Join-код внутри репозитория, в таблице которого происходит изменение.
### Проверка прав с помощью аннотаций
Название говорит само за себя. Проверку прав перед запуском метода было бы удобно делать с помощью аннотаций метода.
### ValueObject и встроенные сущности
Пока самый сложный пункт в этом списке. TypeORM умеет работать с Embedded-значениями, но пока непонятно:
- как сделать их иммутабельными;
- как и где осуществлять их валидацию и как возвращать ошибки клиенту, чтобы он понимал, в каком свойстве какого VO произошла ошибка.
Необходимо определить роль ValueObject в системе и решить: нужны ли они вообще в системе, либо можно обойтись встроенными сущностями и встроенными значениями TypeORM; если нужны, то определить, как должна быть реализована коллекция VO; если не нужны, то определить правила, когда должна использоваться встроенная сущность, а когда встроенное значение. При работе над вопросом учесть, что из-за ограничений TypeORM невозможно будет создать VO со связями (в т. ч. с коллекцией VO)
#### **Мысли**
Для начала необходимо ответить на вопрос - как решить, будет новая группа встроенные полей являться встроенной сущностью, или же ValueObject. Для принятия решения помогут следующие понятия.
**Aggregate Root** - самостоятельная сущность, имеющая собственный репозиторий.
**Entity** (иначе, встроенная сущность) - это отдельная сущность, не имеющая репозитория, находящаяся в отношении композиции со своим родителем и не рассматриваемая отдельно от него. При изменении условий предметной области, либо при более детальном её рассмотрении может быть вынесена в самостоятельную сущность (т. е. в Aggregate Root).
Пример: Рассмотрим предметную область "Магазин автомобилей". Логично определить сущность "Автомобиль" и её вложенная сущность "Двигатель", если ни в каком из UseCase'ов не предполагается рассматривание двигателя отдельно от автомобиля. Предположим, система была реализована и работала, пока не появился новый UseCase: "Пользователь может найти автомобиль по марке двигателя". Это значит, что двигатель отныне может быть рассмотрен отдельно от автомобиля в контексте поиска, поэтому его стоит выделить в самостоятельную сущность. Это, однако, потребует "сжатия" существующей базы, так как если раньше у каждого автомобиля была своя собственный экземпляр двигателя, то сейчас автомобиль должен ссылаться на запись о своей модели двигателя.
Важно заметить, что на практике часто могут возникнуть сомнения, стоит ли делать сущность самостоятельной или вложенной. Лучше отдать предпочтению первому варианту, так как для того, чтобы сделать сущность вложенной нужны веские причины.
**ValueObject** - это несколько (как правило, не более 5) объединенных по смыслу полей, которые не имеет смысла рассматривать вне контекста родителя, и которые вместе формируют единое, неизменяемое понятие. Пример ValueObject: поле "Стоимость" сущности "Автомобиль", котороые хранит "Валюту" и "Значение". Если изменить **любое** из этих полей, то изменится вся стоимость.
Если при анализе предметной области у очередного ValueObject было определно большое количесто полей, то стоит задуматься. Большое количество полей мешает неизменяемости ValueObject, соответственно, нужно либо разделить его на несколко ValueObject, либо определить его как отдельную встроенную сущность.
Важное ограничение ValueObject - он не может иметь отношений с сущностями. "Вкладывание" ValueObject друг-в-друга также стоит производить с осторожностью.
В контексте **TypeORM** ValueObject - это Embedded, а Entity - отдельная сущность с каскадным отношением **OneToOne**.
Коллекцию ValueObject придётся выделять в отдельную таблицу каскадным отношением **OneToMany**.
### Наименование аттрибутов сущностей
Продумать и реализовать наименования аттрибутов в классе и утилиту для их получения; подумать, есть ли способ прокинуть эти наименования в сообщения class-validator
### Использование полей классов сущностей в коде
Подумать, как будет решаться проблема изменения имен полей классов и несовпадении имен полей класса и DTO при вариации
### Работа со встроенными коллекциями сущностией
Реализовать средства для работы (аннотации, менеджер) со встроенными коллекциями в сущностях; если VO в системе будут, то определить, будет ли разница между средствами по работе с коллекцией VO и коллекцией встроенных сущностей
### Хранение информации об измененных полях
Подумать, как будет осуществляться хранение информации об измененных полях сущности (можно делать это либо помощью DomainEvents, либо с помощью событий в UseCase)
---
## Развитие Frontend-архитектуры
### Устройство Redux-хранилища
Продумать, где должны располагаться thunk и обычные action creators (в одном или разных файлах); как выделять action constants (в случае, если они нужны) таким образом, чтобы в них содержалось определение типа возвращаемого действия. Решить, а могут вообще на практике ли произойти случаи, когда нужны action constants (пример - когда действие можно осуществить только через thunk-creator)
### Где делать запросы к API
Сейчас практически все запросы к API сосредоточены в "грязных" (thunk) действиях хранилища, даже если запрос ничего в хранилище не делает (не вызывает dispatch). Такой подход может приводить к проблемам при использовании кода.
### Как писать стили
Сделать окончательный выбор между CSS + BEM и StyledComponents; определить устройство системы стилей
Также, нужно задуматься о внедрении подхода структуризации стилей [Smoothie](https://smoothie-css.com)