Хочу рассказать Java-сообществу Хабра о небольшом, но очень полезном (на личном опыте) фреймворке под названием Green-forest. Данный фреймворк можно использовать как самостоятельно, так и в контексте JEE или Spring.
Как с помощью него можно упростить код приложения узнаем под катом.
В начале рассмотрим проблему для решения которой предназначен Green-forest.
Проблема
Очень часто архитектура крупного проекта имеет следующий вид:
- Слой представления
- Бизнес-слой
- Слой работы с данными
При этом бизнес-слой и слой данных состоят из классов-сервисов, предоставляющих внешнему миру публичное API.
Пример такого сервиса:
public interface UserService {
User createUser(Object someData);
void activateUser(long id);
User updateUser(User updatedUser);
void blockUser(long id);
void unblockUser(long id);
}
С ростом приложения растет количество методов в подобных сервисах — а значит растет громоздкость класса-реализации:
public class UserServiceImpl implements UserService {
//метод на 1-ой строке класса
public User createUser(Object someData) {
//реализация на 10-15 строк
}
//метод 16-ой страке класса
public void activateUser(long id) {
//реализация на 10-15 строк
}
//и т.д.
//метод на 1000-ой строке класса
public void unblockUser(long id) {
//реализация на 10-15 строк
}
}
В итоге появляются классы, содержащие десятки методов и 1000-ти строк кода. Искать и поддерживать код в таких классах становится трудно и муторно.
Классическое решение
Очевидным решением видится разделение одного большого сервиса на множество более мелких:
public interface UserBasicService {
User createUser(Object someData);
void activateUser(long id);
User updateUser(User updatedUser);
}
public interface UserModerationService {
void blockUser(long id);
void unblockUser(long id);
}
Однако, помимо простоты такой подход имеет ряд недостатков:
Трудность равномерного разделения: бывает трудно разделить сервис на равные части. В одном новом сервисе остается 3 метода, в другом — 10-ть.
Неоднозначность разделения: бывает трудно определить в какой дочерний сервис перенести конкретный метод. В результате методы переносятся нелогично и API усложняется для понимания.
Устранение симптомов, но не причины: по сути, мы не решили проблему, т.к. с ростом методов в новых сервисах они тоже рискуют превратиться в громоздкие 1000-ти строчные реализации.
Попробуем решить проблему громоздких сервисом в новом ключе.
Решение через атомарные функции
Раз наше простое разделение сервиса на несколько ему подобных не дает полного решения проблемы, зададимся вопросом, а можем ли мы разделить его на более атомарные сущности? Конечно же можем! Ведь целью любого сервиса являются его методы, а значит, создав множество отдельных методов, мы избавимся от озвученных выше недостатков:
Нет проблем неравномерного и неоднозначного разделения: множество методов разделено равномерно и однозначно по своему определению.
Решена проблема роста: при появлении новых методов мы просто создаем новые классы, не увеличивая размер старых.
Реализация решения в Green-forest
В Green-forest Framework применяется архитектура Action-Handler, иллюстрируемая схемой:
Рассмотрим компоненты этой схемы:
-
Action
Класс Action представляет атомарный «метод», который можно «вызвать». Внутри себя класс содержит входные и выходные данные:
//Класс Action с типом входных данных - String и выходных - Integer public class SomeAction extends Action<String, Integer>{ public SomeAction(String input) { super(input); } }
-
Handler
Класс Handler содержит конкретную реализацию для данного Action типа:
@Mapping(SomeAction.class) public class SomeHandler extends Handler<SomeAction>{ @Inject SomeService service; public void invoke(SomeAction action) throws Exception { String input = action.getInput(); Integer result = service.doSomeWork(input); action.setOutput(result); } }
-
Framework
Объект фреймворка Engine обеспечивает связь между Action и Handler объектами:
//создаем объект Engine Engine engine = new Engine(); //регистрируем обработчик engine.putHandler(SomeHandler.class); //выполняем действие Integer result = engine.invoke(new SomeAction("some data"));
Пример использования
Итак, мы рассмотрели ключевые классы фреймворка. Используя их, проведем рефакторинг примера с нашим сервисом UserService.
В начале отнаследуем UserService от служебного интерфейса ActionService:
import com.gf.service.ActionService;
public interface UserService extends ActionService {
//интерфейс не содержит методов - они отнаследованы от ActionService
}
Далее создадим множество Action классов:
import com.gf.Action;
public class CreateUser extends Action<Object, User> {
public CreateUser(Object someData){
this(someData);
}
}
public class ActivateUser extends Action<Long, Void> {
public ActivateUser(Long id){
this(id);
}
}
/*
и т.д для классов:
UpdateUser.java
BlockUser.java
UnblockUser.java
...
*/
Осталось создать обработчики для данных классов. Например:
@Mapping(CreateUser.class)
public class CreateUserHandler extends Handler<CreateUser>{
public void invoke(CreateUser action) throws Exception {
Object input = action.getInput();
User user = ...
action.setOutput(user);
}
}
Пример конечного вида реализации UserServiceImpl:
import com.gf.Action;
import com.gf.core.Engine;
public class UserServiceImpl implements UserService {
Engine engine;
public UserServiceImpl(){
engine = new Engine();
//регистрируем Handler классы по имени пакета
engine.scanAndPut("some.package");
}
public <I, O> O invoke(Action<I, O> action) {
return engine.invoke(action);
}
public <I, O> O invokeUnwrap(Action<I, O> action) throws Exception {
return engine.invokeUnwrap(action);
}
}
В итоге мы разделили большой интерфейс UserService на множество Action классов, а реализацию UserServiceImpl — на множество Handler классов.
Выводы и ссылки
Мы рассмотрели проблему концентрации логики в одном классе и способы решения, включая разделение на атомарные классы-методы. Далее мы познакомились с Green-forest Framework, который реализует последний подход с помощью архитектуры Action-Handler. Используя Green-forest, вы можете упростить и структурировать код своего приложения.
Приятной работы, коллеги!
Ссылки:
Green-forest Framework
Документация
Автор: edolganov