Статьи

Ленивая загрузка — запах кода

Вы когда-нибудь видели эти огромные объекты со многими атрибутами? Эти доменные объекты, где мы используем отложенную загрузку, потому что мы не хотим получать слишком много информации из базы данных? Могу поспорить, что вы получили это сомнительное удовольствие.

Сегодня я хочу поделиться с вами своими впечатлениями о них — использование ленивой загрузки должно восприниматься как запах кода!

Позвольте мне объяснить себя:

  • Ленивая загрузка означает, что иногда вам не понадобятся некоторые атрибуты объекта. Эти атрибуты будут необходимы в другом контексте. Разве это не значит, что вы строите разные объекты в зависимости от контекста?
  • Функциональность, которая использует этот объект, знает слишком много. Он знает API объекта, и этот API также содержит методы, которые требуют атрибутов, которые не были загружены. Отлично, не правда ли?
  • Вы должны помнить, что нужно в каждом месте, а что не нужно …
  • … и, что еще хуже, вы должны помнить, что вы можете использовать и какие методы не поддерживаются в определенном месте.

В случае, если вам этого недостаточно, позвольте мне уточнить.

Как работает отложенная загрузка

Короче говоря, отложенная загрузка позволяет НЕ загружать потомков при загрузке родителя. Он загружает их только тогда, когда вы явно просите об этом.

Как это работает? Давайте посмотрим на простой пример:

01
02
03
04
05
06
07
08
09
10
11
class User {
 private final Name name;
   
 @OneToMany(fetch = FetchType.LAZY)
 private List<Role> roles;
  
 @OneToMany(fetch = FetchType.LAZY)
 private List<Subscription> subscriptions;
 
 // Some more attributes and methods
}

Что говорит вам определение этого класса? Что FetchType.LAZY значит для нас? Это дает нам информацию о том, что списки, содержащие роли пользователя и подписки, не будут заполняться данными до тех пор, пока мы явно не запросим такие данные.

Что такое ограниченный контекст?

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

Код пахнет? Но … почему?

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

Давайте еще раз посмотрим на наш класс:

01
02
03
04
05
06
07
08
09
10
11
class User {
 private final Name name;
   
 @OneToMany(fetch = FetchType.LAZY)
 private List<Role> roles;
  
 @OneToMany(fetch = FetchType.LAZY)
 private List<Subscription> subscriptions;
 
 // Some more attributes and methods
}

Можете ли вы рассказать мне что-нибудь еще об этом объекте, кроме вещей, которые уже были упомянуты?

Мы знаем, что работаем с классом, чьи объекты используются в местах, где роли могут быть необходимы, но это не обязательно. Где могут быть необходимы подписки, но не обязательно. Имя всегда обязательно.

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

Но … мы должны пройти через код, чтобы найти эти места. Это требует времени и усилий. К сожалению, есть шанс, что мы пропустим некоторые места.

То, что мы знаем … то, что мы не знаем …

Не лучше ли знать, где и что требуется? Конечно, это будет! Вопрос: как этого добиться?

Давайте сделаем краткий анализ нашего примера:

01
02
03
04
05
06
07
08
09
10
11
class User {
 private final Name name;
   
 @OneToMany(fetch = FetchType.LAZY)
 private List<Role> roles;
  
 @OneToMany(fetch = FetchType.LAZY)
 private List<Subscription> subscriptions;
 
 // Some more attributes and methods
}

Мы уже знаем несколько вещей:

  • Имя всегда обязательно.
  • Иногда нам нужны роли.
  • Иногда нам нужны подписки.

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

Это все об информации. Сейчас время для неизвестных:

  • Есть ли место, где нам нужны роли и подписки?
  • Нужны ли роли и подписки в разных местах?
  • Есть ли место, где мы тоже не нуждаемся?
  • Зависит ли это от контекста, какие атрибуты будут необходимы?

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

Давайте улучшим код

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

Хорошо, но как мы можем улучшить код из примера?

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

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

Теперь мы можем реорганизовать класс User и разделить его на что-то более простое для понимания:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class AuthUser {
 private final Name name;
 private List<Role> roles;
 
 // Some more attributes and methods
}
 
class ReportUser {
 private final Name name;
 private List<Subscription> subscriptions;
  
 // Some more attributes and methods
}
 
class ApplicationUser {
 private final Name name;
 
 // Some more attributes and methods
}

Теперь у нас есть три класса вместо одного, но у нас также есть больше информации в нашем коде. Нам не нужно будет проходить через код, чтобы узнать, что и где необходимо. Было бы достаточно, чтобы открыть определение класса

Что дальше?

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

Если у вас уже есть загруженные ссылки в вашем домене, вы должны рефакторинг только той части, с которой вы уже работаете. Вы минимизируете как риск изменения, так и усилия, необходимые для его внесения. И код в любом случае станет более наглядным.

Удачи!

Ссылка: Lazy-loading — это запах кода от нашего партнера по JCG Себастьяна Малаки из блога « Давайте поговорим о Java» .