Знакомство с Green-forest Framework

в 16:10, , рубрики: ioc контейнеры, java, jee, open source, spring, метки: , , , ,

Green-forest Logo
Хочу рассказать 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, иллюстрируемая схемой:
Знакомство с Green forest Framework

Рассмотрим компоненты этой схемы:

  1. Action

    Класс Action представляет атомарный «метод», который можно «вызвать». Внутри себя класс содержит входные и выходные данные:

    //Класс Action с типом входных данных - String и выходных - Integer
    public class SomeAction extends Action<String, Integer>{
     
        public SomeAction(String input) {
            super(input);
        }
         
    }

  2. 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);
        }
    }
  3. 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js