Статьи

Обширные доменные объекты и Spring Dependency Injection совместимы

В настоящее время я работаю в среде, где большинство разработчиков являются объектно-ориентированными фанатиками. Учитывая, что мы развиваемся на Java, я думаю, что это хорошая вещь — сохранить часть фанатиков. В частности, я столкнулся с глубоко укоренившимся мемом, в котором говорится, что моделирование Rich Domain Objects и одновременное использование Spring-зависимостей невозможно. Это не только совершенно неверно, но и показывает недостаток знаний о функциях Spring, которые я попытаюсь исправить в этой статье.

Тем не менее, моя главная мысль не о Spring, а о какой-либо парадигме, которая наиболее ценится, будь то объектно-ориентированное программирование, функциональное программирование, аспектно-ориентированное программирование или что-то еще. Они предназначены только для того, чтобы придать программам желаемые свойства: тестируемые на модуле, читаемые, что угодно … Поэтому всегда следует сосредоточиться на этих свойствах, а не на способе их получения. Помнить:

Когда мудрец указывает на луну, идиот смотрит на палец.

Вернемся к проблеме под рукой. Основная идея состоит в том, чтобы какой-то компонент имел ссылку на какой-либо сервис для выполнения каких-то задач (обратите внимание на реальные слова, стоящие за этой идеей…), чтобы вы могли вызвать:

someBean.doStuff()

Вот что именно не нужно делать:

public class TopCaller {
 
    @Autowired
    private StuffService stuffService;
 
    public SomeBean newSomeBean() {
 
        return new SomeBeanBuilder().with(stuffService).build();
    }
}

Эта конструкция должна быть проклятием для разработчиков ,
способствующих юнит-тестирование как новые () глубоко подсоединяет
TopCallerи
SomeBeanBuilderклассов. Нет возможности заглушить эту зависимость в тесте, она не позволяет тестировать
createSomeBean()метод изолированно.

Что вам нужно, это ввести
SomeBeanпрототипы в
SomeBeanBuilderсинглтон. Это внедрение метода и возможно в Spring с помощью методов поиска (я уже писал об этом некоторое время назад, и вам, вероятно, стоит взглянуть на это).

public abstract class TopCaller {
 
    @Autowired
    private StuffService stuffService;
 
    public SomeBean newSomeBean() {
 
        return newSomeBeanBuilder().with(stuffService).build();
    }
 
    public abstract SomeBeanBuilder newSomeBeanBuilder();
}

С правильной конфигурацией Spring будет заботиться о предоставлении разных значений при
SomeBeanBuilderкаждом
newSomeBeanBuilder()вызове метода. С этим новым дизайном мы изменили сильную связь между
TopCallerи
SomeBeanBuilderна мягкую связь, которая может быть заглушена в тестах и ​​позволяет проводить модульное тестирование.

В текущем проекте, по- видимому, единственная причина для SomeBeanBuilderсуществовать, чтобы пройти StuffServiceот TopCallerк SomeBeanэкземплярам. Нет необходимости хранить его методом инъекции.

Есть два возможных улучшения:

  1. Учитывая наши «новые» знания, введите:

    • Способ newSomeBean()вместо того , чтобы newSomeBeanBuilder()вTopCaller
    • StuffService прямо в SomeBean

  2. Сохранить StuffServiceкак TopCallerатрибут и передавать его каждый раз, когда doStuff()вызывается

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

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

Я надеюсь, что этот кусок доказал вне всякого сомнения, что Spring не мешает богатой доменной модели, отнюдь нет. Как правило, вы должны знать об инструментах, которые вы используете, и идти своим путем, а не идти по какому-то пути ради него.