Работая над одним из наших внутренних инструментов, я решил сделать небольшой набор и не следовать моему собственному совету. Мы создаем мини-инструмент CRM, и первоначальные требования были следующими:
- Вести информацию о компаниях, с которыми мы имеем дело;
- Вести список контактов по каждой компании;
- Вести список заданий (проекты, обучение, консультации) для каждой компании.
ПРИМЕЧАНИЕ. Я опущу детали кода, атрибутов и т. Д., Чтобы сделать этот пост простым.
Начав с малого, создавая CRUD для компании, я получил компанию, которая выглядела следующим образом:
1
2
3
4
|
class Company { + id: CompanyId + name: String } |
Это было все хорошо и хорошо. Затем мне нужно было написать код, чтобы вести список контактов для каждой компании. Я закончил со следующим:
01
02
03
04
05
06
07
08
09
10
11
12
|
class Contact { + id: ContactId; + companyId: CompanyId; + name: String + email: String; } class Company { + id: CompanyId; + name: String; + contacts: List[Contact]; } |
Это было начало проблем. На странице «Просмотр компании» мне нужно было отобразить данные, связанные с компанией и всеми ее контактами. Для страниц, которые имели дело только с данными Компании (страница со списком всех компаний, страница для редактирования / удаления компании), мне не требовалась информация о контактах. Должен ли я загружать контакты каждый раз, когда загружаю компанию? Я не должен загружать их? Проблема не загружать контакты в некоторых случаях заключается в том, что по мере развития кода я бы не знал, был ли список контактов внутри компании пустым, потому что у компании нет контактов или они не были загружены. Это сбивает с толку. Поскольку производительность в этом приложении не имеет значения, я решил загружать список контактов каждый раз, когда мне нужна компания. Проблема решена.
В следующей функции я должен был поддерживать обязательства (CRUD) для компании. Следуя тому же подходу, который я использовал для контактов, я получил следующие объекты:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
class Engagement { + id: EngagementId; + companyId: CompanyId; + name: String + startDate: Date; + endDate: Date; + description: String; } class Company { + id: CompanyId; + name: String; + contacts: List[Contact]; + engagements: List[Engagement]; } |
На данный момент все стало очень запутанным. У меня были страницы, которые требовали компании, ее контактов и обязательств. Страницы, которые требовали только компанию и вовлечение, страницы, которые требовали только компанию и контакты. Но проблемы были связаны не только с тем, что загружать и где. У меня было много кода, который полагался на структуру компании.
Приложение представляет собой веб-приложение, использующее AngularJS спереди, JSON направляется в браузер и возвращается в приложение. Для этого у меня были JSON-конвертеры, которые конвертировали бы JSON в объекты и из них. У меня также было довольно много тестов для моего API и внутренних уровней, которые использовали бы сборщики для сбора данных. Таким образом, было достаточно много кода, который, чтобы удовлетворить все функции, будет опираться на структуру компании. Этот код «должен был знать», когда контакты и обязательства были загружены или нет. И, конечно, это постоянно менялось, пока мы решали, сколько информации нам нужно на каждой странице.
Когда функции стабилизировались, и я сделал еще несколько изменений в коде, все работало.
Волновой эффект
Поскольку мы думали, что готовы начать создавать другие функции (панель инструментов, финансовую информацию, прогнозы, заметки, напоминания, последующие действия и т. Д.), Мы поняли, что упустили что-то важное.
Некоторые из наших проектов осуществляются через партнеров (другие компании). Это означает, что участие может включать более одной компании. Это может сделать отношения между Компанией и Engagements несколько иными. Возможно, отношения между Компанией и Engagements больше не будут для многих . Вероятно, будет много ко многим .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
class Engagement { + id: EngagementId; + companies: List[Company]; + name: String + startDate: Date; + endDate: Date; + description: String; } class Company { + id: CompanyId; + name: String; + contacts: List[Contact]; + engagements: List[Engagement]; } |
Я думал, что это будет легкое изменение, но я был удивлен, увидев огромный эффект ряби, который он имел в моем коде. Это повлияет на нагрузку тестовых данных, компоновщиков, анализаторов JSON и структуры API, и это нехорошее чувство. Я был очень разочарован собой и очень разозлился, если честно.
Следуя моему собственному совету
Несколько лет назад я столкнулся с CQS, а затем с CQRS . Вначале я не уделял CQS особого внимания, и только с CQRS я понял другой способ разработки программного обеспечения. С тех пор я был активным сторонником отделения структур данных (и да, я трактую сущности как структуры данных), которые я использую для записи, и те, которые я использую для чтения. Я не говорю о независимо развертываемых моделях чтения / записи, различных базах данных, событиях, сообщениях и т. Д. Я говорю только об использовании различных объектов для записи и чтения данных.
Исправление проблемы (1-е решение)
После другого обсуждения мы решили, что Обязательство всегда будет для Компании, но, возможно, оно пришло к нам через партнера. Это снова изменило ситуацию. Итак, я решил сделать следующее: удалить все зависимости (атрибуты, содержащие другие сущности или список сущностей) от всех сущностей. Тогда закончилось так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
class Company { + id: CompanyId + name: String } class Contact { + id: ContactId; + companyId: CompanyId; + name: String + email: String; } class Engagement { + id: EngagementId; + companyId: CompanyId; + partnerId: Optional[CompanyId]; + name: String + startDate: Date; + endDate: Date; + description: String; } |
При таком подходе объекты будут содержать только те данные, которые им необходимы для сохранения.
Но я все еще должен был исправить запросы, где многим из них понадобилась бы комбинация этих объектов. Для этого я создал «прочитанные» объекты, которые будут содержать именно те данные, которые необходимы для каждого запроса. Некоторые из них выглядели так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class CompanyWithContacts { + company: Company; + contacts: List[Contacts] } class CompanyWithContactsAndEngagements { + company: Company; + contacts: List[Contacts]; + engagements: List[Engagements]; } class EngagementWithCompanies { + engagement: Engagement; + client: Company; + partner: Optional[Company]; } |
При таком подходе каждый запрос будет возвращать комбинацию данных, запрошенных механизмом доставки (страницы на веб-сайте). Изменения в том, как мои сущности связаны друг с другом, больше не вызывали волнового эффекта изменений, потому что только определенные запросы могли бы прерваться. Больше не было проблем с отложенной загрузкой. Не было никаких сомнений в отношении пустых атрибутов, поскольку атрибутов больше не было. Дополнительные можно легко пометить как дополнительные (спасибо Java 8).
Исправление проблемы (2-е решение)
После исправления, приведенного выше, я был достаточно счастлив, так как смог локализовать изменения, когда изменились отношения сущностей. Но это было немного больше. С другой стороны, они позволили мне сделать один звонок со страницы, где требовалась комбинация данных. С отрицательной стороны, производительность не была для меня реальной проблемой, и я не хотел, чтобы эти дополнительные объекты со странными именами висели вокруг. Мне все еще нужно было написать код, чтобы заполнить их и преобразовать в JSON.
Затем я решил сделать несколько звонков со своих страниц. Если на странице нужна компания, список контактов и список обязательств, связанных с этой компанией, я бы сделал три звонка со страницы. Это решение заставило все объекты «чтения» исчезнуть, и мой код все еще оставался очень простым.
Вывод
Держите свои объекты отделенными друг от друга и сосредоточьтесь на реализации простых запросов от клиента. Просто перейдите к одному запросу, если производительность действительно окажется проблемой.
Не используйте ORM. ORM сделали бы мои изменения еще хуже, так как мне пришлось бы синхронизировать свои сущности и базу данных. Замечательно иметь свободу получать набор записей из базы данных, используя любой запрос, и заполнять ваши объекты так, как вы хотите.
То, как мы запрашиваем данные, меняется гораздо чаще, чем то, как мы сохраняем данные, и эти изменения могут разрезать и нарезать данные по-разному. Связывание ваших сущностей только усложнит выполнение всех запросов и создаст ненужную нагрузку на код, который должен только выполнять бизнес-логику и хранить данные.
Ссылка: | Увеличивая сложность по одному объекту от нашего партнера JCG Сандро Манкузо в блоге Crafted Software . |