Как стать автором
Обновить

Комментарии 31

Чем это оличается от:
@Service
class CreateUserService {
  public void createUser(...)
}
@Service
class ActivateUserService {
  public void activateUser(..)
}

Action<From, To> — если у меня есть 3 аргумента — нужно их оборачивать в еще 1н класс?
Рассмотрим ваш подход:
@Service
class CreateUserService {
  public void createUser(...)
}
@Service
class ActivateUserService {
  public void activateUser(..)
}


Плюсы:
очевидны — остаемся в рамках той парадигмы, что и были

Минусы:
1. В реале придется писать не только class, но и interface (ведь в классической парадигме мы отдаем наружу интерфейсы).

Т.е. у вас будет:
interface CreateUserService {}

class CreateUserServiceImpl{}

interface ActivateUserService{}

class ActivateUserServiceImpl{}


Получается, что сложность того же порядка:
  • интрефейс + реализация + метод
  • Action + Handler + метод

Так что в классическом подходе вы не сэкономили.

2. Посмотрим на клиент:
В классическом подходе придется писать:
class Client {

  @Autowired
  CreateUserService createUser;

  @Autowired
  ActivateUserService activateUser;

  @Autowired
  UpdateUser updateUser;

  ...

}


В подходе же Action-Handler:
class Client {

  @Autowired
  UserService userService;

}


Т.о. у нас для внешнего мира всё так же один интерфейс UserService.
Просто теперь у него — «универсальное» API:
userService.invoke(new CreateUser(...));
userService.invoke(new ActivateUser(...));
«Action<From, To> — если у меня есть 3 аргумента — нужно их оборачивать в еще 1н класс?»

Да, к сожалению, за всё надо платить.
Если у вас входные параметры — это несколько разных классов, то придется делать класс-обертку для них.
Самый быстрый способ — создать этот класс внутри самого Action:
public class CreateUser extends Action<CreateUser.Input, User>{
	
	
	public static class Input {
		
		public Object someData1;
		public Object someData2;

		public Input(Object someData1, Object someData2) {
			this.someData1 = someData1;
			this.someData2 = someData2;
		}
		
	}

	public GetDeletedNodesByDate(Input input) {
		super(input);
	}
	
}
Простите, опечатка в коде: готовил пример с живого проекта :)
Правильный код:
public class CreateUser extends Action<CreateUser.Input, User>{
	
	
	public static class Input {
		
		public Object someData1;
		public Object someData2;

		public Input(Object someData1, Object someData2) {
			this.someData1 = someData1;
			this.someData2 = someData2;
		}
		
	}

	public CreateUser(Input input) {
		super(input);
	}
	
}
похоже стало на каждое действие свой класс, а раньше был отдельный метод, в одном классе как вариант.
Вместо одного файла с кучей методом будет много файлов. Структурно же ничего не изменилось. Если раньше для навигации по методам можно было использовать вкладку Структура в IDEA, например, то теперь — файловое дерево. Разницы никакой.
Если рассматривать только вопрос навигации, то раньше нужно было сначала искать класс в пакетах, потом метод в классе.
в новом подходе — нужно искать только класс. Причем пакеты древовидны, в отличие от линейных методов:
some/package/user/
some/name/user/basic
		CreateUser.java
		UpdateUser.java
		...
some/name/user/moderation
		BlockUser.java
		UnblockUser.java
		...
Простите, опечатка: первая строка some/package/user/ — лишняя
НЛО прилетело и опубликовало эту надпись здесь
Это называется Builder. Фреймворк для этого не нужен.

В целом, вопрос «удобства» — сугубо индивидуален. Лично мне создание кучи классов кажется ненужным.
Жуть какая, без IDE в этом будет уже не разобраться. А я всё ещё частенько правлю свои проекты в vim, а то и в nano… За такой код многие и не любят энтерпрайзную джаву…

В нашем фреймворке мы наоборот пошли. DI в рантайме связывает всё через провайдеры, никаких интерфейсов для классов писать не надо. См. DI из Google Guice. В итоге у нас есть хэндлеры, фильтры и DAO. Интерфейсы тогда и только тогда, когда есть более одной имплементации интерфейса.

А ещё больше мне нравится DI в Scala через trait'ы Тоже никаких интерфейсов, ещё меньше кода, DI-связывание на этапе компиляции…
Если хочется еще меньше кода, переходите на функциональные языки.
Вы имеете ввиду Scala? А вы читали мой комментарий? :)
Scala, в первую очередь, объектно-ориентированый, а уж потом функциональный. Я говорю про чисто функциональные языки, вроде Clojure.
Scala — мультипарадигменный язык. Впрочем, против Clojure я ничего, конечно, не имею. Хоть мне и не понятна его бешеная популярность.
НЛО прилетело и опубликовало эту надпись здесь
Эм, вы видели юнит-тест-фреймворки в последние лет 10-15? То есть EasyMock.createStrictMock(UserService.class) совершенно индифферентно интерфейс UserService или конкретный класс. Так что если мне надо протестировать какой-нибудь AccountService, зависящий от UserService, я точно не буду инициализировать UserService. Это будет обычный mock.
НЛО прилетело и опубликовало эту надпись здесь
Хорошо, в последние 10 лет вы что-то видели, а в последние 5? PowerMock 1.0 был выпущен в 2008-м году. И приватные методы, и финальные, и конструктор он легко перегружает. И статические методы, кстати, тоже. И вовсе это не чудеса, несложная манипуляция с байт-кодом.

Писать интерфейс, зная, что будет только одна его реализация — это нарушение принципа DRY. Да, без интерфейса я буду «ограничен» в публичных методах — любой из них может быть вызван снаружи. Но это заставляет меня думать об уровнях доступа, в моих классах нет публичных методов, которые ниоткуда не вызываются извне.

Кстати, мои классы всегда решают ровно одну задачу. Так что другое применение интерфейсов — разделение логических сущностей — мне тоже не нужно.

Я использую интерфейсы строго по их ООП-назначению. У меня может быть интерфейс Cache, с реализациями MemcacheCache, EhcacheCache, RedisCache и т.д., но никогда нет AccountService и AccountServiceImpl. По-моему, если классу не получается придумать название и приходится дописывать бессмысленное «Impl», то с иерархией классов что-то не так.
На сайте проекта написано, что это просто IoC контейнер для Джавы. Зачем вы применяете его для организации кода в сервисах?
Так же на сайте проекта написано, что этот IoC контейнер можно (и нужно) использовать вместе с JEE и Spring.
И даже даны примеры этой интеграции тут и тут.
У меня вопрос что мешает наследоваться от Spring-Data репозиториев? Там эта проблема существенно удобнее решена. Я бы понял если бы можно было декларативку наколбасить для простых функций. А так не понятно зачем все это.
Сначала был один класс с кучей методов, а будет один пакет с кучей классов, каждый из которых содержит по одному методу.Проблема просто переходит в другую плоскость, добавив кучу лишнего кода.Надо нормально проектировать архитектуру, а не пытаться щели замазать очередным фреймворком.Есть антипатерн — бескровная доменная модель.Стоит смотреть в этом направлении, если в сервизах стопилось слишком много методов.
НЛО прилетело и опубликовало эту надпись здесь
В общем, это просто реализация паттерна «Command».

Фреймворк не нужен.
Да, паттерн «Command» наиболее близок к «Action-Handler».
Но, реализуя свои собственные комманды, вы столкнетесь с необходимостью передавать в них контекст, с желанием добавить перехватчики вызовов, фильтры и т.д. Все эти вещи как раз реализованы в представленном фреймворке.
На практике я пока мог переиспользовать только простые либы, базовая часть все-равно меняется от проекта к проекту. Обязательно вылезет что-нибудь, что выйдет за рамки предыдущей реализации.
Перехватчики вызовов, фильтры? АОП спешит на помощь!
Мне кажется, что использование команд реально оправдана и удобна в том случае, если с этими командами нужно что-то впоследствии делать (как с сущностями). Ревертить, класть в очередь, сохранять историю и тому подобные вещи. Для замены же обычной сервисной архитектуры этот подход несколько спорный.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории