Pull to refresh

Comments 9

Предложу еще одно решение поставленной в задаче проблемы:
Итак у нас есть сервис, в котором куча «методов-скриптов».
Если количество этих методов уже неудобно, то можно выносить их в отдельные классы-команды.

Пример
class CreateApplication extends BaseCommand<Application, Result>{

    public Application app;
    public InvocationContext context;

    public CreateApplication(Application app){
         this.app = app;
    }

    public Result invoke(){
          //здесь делаем логику создания в возвращаем результат
          //используем context
    }

}


Тогда у сервиса будет универсальный метод invoke:
   
   public <T> invoke(BaseCommand<?, T> command){
       command.context = ...; //проставляем контекст выполнения
       return command.invoke();
   }



Как видите, здесь мы создаем команду где-то извне, передаем ее сервису, сервис проставляет свой контекст исполнения (например контекст может содержать классы DAO) и дальше вызывает команду на исполнение.

У такого подхода есть свои плюсы, минусы и вариации.
Но в целом он тоже применим.

Как пример реализации такого подхода, я даже когда-то написал свою библиотеку для Java.
Однако, опыт показывает, что в 90% случаев проще использовать простой подход: один-два-много God Сlass-ов с кучей публичных методов в каждом из них.

Да будет немного муторно, когда методов 20-30.
Но это нормальная цена за простоту — любой джуниор сразу понимает, как это работает.:)

Я не призываю использовать только такой подход, нет!
Мысль моя в том, что обычно этого достаточно. А когда проект станет большим, то тогда отдельные участки проекта можно поменять на что-то другое.
Тут ключевым моментом является возможность поменять отдельные участки проекта, а что-бы что-то менять, нужна какая-то степень уверенности, что система продолжит работать. Unit тесты дают такую уверенность, но вряд ли senior или junior захочет / сможет писать и поддерживать тест для класса c 30 методами и плохим cohesion.
Про юнит-тесты не соглашусь. Если мы раскидываем код из God Сlass-а в кучу разных других классов — то мы наводим некий порядок, но не уменьшаем сложность всей системы.

Юнит тесты для класса с 30 методами — это условно 30 разных тестов для каждого отдельного метода.

Юниты тест для разделенных 6 разных классов с 5 методами в каждом — это те же 30 тестов.

Т.о. как у вас было 30 тестов, так и придется поддерживать те же 30 тестов.
Да, если все методы абсолютно независимы, однако это значит либо много копи-паста, либо вынос общeй логики в отдельный класс. Если мы идем по второму пути, то мы возвращаемся к изначальной проблеме — либо эволюционный дизайн, либо правило о там, где этот новый класс должен находится и как его называть.

Кроме того, технически, большой класс скорее всего будет иметь множество зависимостей, для которых надо делать Mocks. В случае добавления новой функциональности (нового метода) с новой зависимостью, упадут все 30 тестов (точнее уже 31), а не один новый, что несколько противоречит сути unit тестов.

Конечно, я не говорю, что такие проекты не могут существовать, но я не согласен, что в 90% случаев подход с большими классами выгоднее. Не претендую на истину, но мой личный опыт показывает как-раз обратное (в области enterprise, про которую я писал). Система, которую разрабатывали порядка 10-15 человек около года, становится уже слишком дорогой для дальнейшей разработки через год-два после первого релиза.
Хорошее решение (если правильно называть команды), но оно применимо, если у нас уже есть проблема с cohesion. Я скорее писал о решение, которое поможет уменьшить вероятность возникновения такой проблемы.
Попытаюсь сказать по-другому:
«проблема с cohesion» — это обычно не такая критичная проблема, чтобы решать ее на каждом проекте. Да, вас может раздражать что один класс содержит 2 тысячи строк кода и лучше бы этот код разбить на отдельные классы.

Но проекты с такими God Сlass-ами вполне нормально живут, для них пишутся такие же юнит-тесты как и для меньших классов.

Потому что мы все равно разнесли логику на слои. Контроллеры получают запросы, DAO общаются с БД, а God Сlass-ы бизнес-логики содержат все сценарии работы, которые нужны от системы.

Я не говорю что God Сlass-ы — это хорошо. Я говорю, что они терпимы и с ними можно жить.
Ну, вы практически переизобрели CQRS. Только Info нужно заменить на Query, а Action на Command. Что касется CRUD-сервисов, то вообще не очень понятно зачем они нужны. Можно взять один GenericDao (точнее Repository), для работы со всеми типами сущностей. Там, где его возможностей не хватает, уже делать свои Command/Query.
Поправьте меня, если я не прав, но CQRS говорит о характеристиках метода, а не класса или системы, хотя, безусловно, классы можно разбивать на основе этого принципа. Что касается CRUD и GenericDao — тут я согласен, что можно, вопрос удобно ли это? Когда-то — да удобно, когда-то — нет, зависит от конкретной системы.

Возможно, пример, приведенный в статье, оказался не самый удачный. Я не настаиваю на использовании CRUD или иного принципа деления функционала, основная идея — разбиение сервисов на двум координатам на этапе проектирования.
Sign up to leave a comment.

Articles