В последние годы мы часто слышали просьбу (как от сообщества, так и от наших клиентов) о том, как поменять логику постоянства Activiti с реляционной базы данных на что-то другое. Когда мы анонсировали Activiti 6, одним из обещаний было то, что мы сделаем именно это возможным.
Люди, которые погрузились в код движка Activiti, будут знать, что это серьезный рефакторинг, поскольку постоянный код тесно связан с обычной логикой. В основном в Activiti v5 были:
- Классы сущностей : они содержат данные из базы данных. Обычно одна строка базы данных является одним экземпляром сущности
- EntityManager : эти классы группируют операции, связанные с сущностями (поиск, удаление,… методы)
- DbSqlSession : низкоуровневые операции (CRUD) с использованием MyBatis . Также содержит кэши длительности команд и управляет сбросом данных в базу данных.
Проблемы в версии 5 были следующие:
- Нет интерфейсов. Все является классом, поэтому замена логики становится действительно сложной.
- Низкоуровневая DbSqlSession использовалась повсюду в базе кода.
- большая часть логики для сущностей содержалась в классах сущностей. Например, посмотрите на метод завершения TaskEntity . Вам не нужно быть экспертом Activiti, чтобы понять, что это не очень хороший дизайн:
- Это запускает событие
- это вовлекает пользователей
- Вызывает метод для удаления задачи
- Продолжает процесс, вызывая сигнал
Не поймите меня неправильно. Код v5 продвинул нас очень далеко и привел в действие множество удивительных вещей по всему миру. Но когда дело доходит до замены слоя постоянства … это не то, чем можно гордиться.
И, конечно же, мы могли бы взломать наш путь к коду версии 5 (например, заменив DbSqlSession чем-то нестандартным, которое отвечает используемым там методам / именам запросов), но это все равно было бы не совсем красиво с точки зрения дизайна и довольно реляционная база данных. И это не обязательно соответствует технологии хранилища данных, которую вы могли бы использовать.
Нет, для версии 6 мы хотели сделать это правильно . И, о боже … мы знали, что это будет много работы … но это было еще больше работы, чем мы могли себе представить (просто посмотрите на коммиты в ветке v6 за последние пару недель). Но мы сделали это … и конечный результат просто прекрасен (я предвзят, правда). Итак, давайте посмотрим на новую архитектуру в v6 (простите мне мои картинки Powerpoint. Я кодер, а не дизайнер!):
Итак, там, где в v5 не было интерфейсов, везде есть интерфейсы в v6. Приведенная выше структура применяется для всех типов сущностей в движке (в настоящее время около 25). Так, например, для TaskEntity есть TaskEntityImpl , TaskEntityManager , TaskEntityManagerImpl , TaskDataManager и класс TaskDataManagerImpl (и да, я знаю, что они все еще нуждаются в javadoc). То же самое относится ко всем лицам .
Позвольте мне объяснить диаграмму выше:
- EntityManager : это интерфейс, с которым весь остальной код взаимодействует, когда дело касается данных. Это единственная точка входа, когда речь идет о данных для конкретного типа объекта.
- EntityManagerImpl : реализация класса EntityManager. Операции часто выполняются на высоком уровне и выполняют несколько задач одновременно. Например, удаление Execution может также удалять задачи, задания, identityLinks и т. Д. И запускать соответствующие события. Каждая реализация EntityManager имеет DataManager. Всякий раз, когда ему нужны данные из постоянного хранилища, он использует этот экземпляр DataManager для получения или записи соответствующих данных.
- DataManager: этот интерфейс содержит операции «низкого уровня». Обычно содержит методы CRUD для типа сущности, которым он управляет, и конкретные методы поиска, когда необходимы данные для конкретного варианта использования
- DataManagerImpl : реализация интерфейса DataManager. Содержит фактический код персистентности. В v6 это единственный класс, который теперь использует классы DbSqlSession для связи с базой данных с использованием MyBatis. Обычно это тот класс, который вы хотите поменять.
- Entity : интерфейс для данных. Содержит только геттеры и сеттеры.
- EntityImpl : реализация вышеуказанного интерфейса. В Activiti v6 это обычное pojo, но интерфейс позволяет переключаться на различные технологии, такие как Neo4 с spring-dataj, JPA,… (которые используют аннотации). Без этого вам нужно будет обернуть / развернуть объекты, если реализация по умолчанию не будет работать на вашей технологии постоянства.
Укрепление
Перемещение всех операций в интерфейсы дало нам четкое представление о том, какие методы были распределены по базе кода. Знаете ли вы, например, что существует по крайней мере пять различных способов удаления Выполнения (названных «удалить», «удалить», «уничтожить» и т. Д.) Они сделали почти то же самое, но с небольшими различиями. Или иногда не совсем тонкий.
Большая часть работы за последние недели включала объединение всей этой логики в один метод. Теперь, в текущей кодовой базе, есть только один способ сделать что-то. Что очень важно для людей, которые хотят использовать разные технологии персистентности. Заставить их реализовать все разновидности и тонкости было бы безумием.
Реализация в памяти
Чтобы доказать возможность подключения слоя постоянства, я сделал небольшой прототип «в памяти». Это означает, что вместо реляционной базы данных мы используем простые старые HashMaps для хранения наших сущностей как {entityId, entity}. Затем запросы становятся условными предложениями.
- Код можно найти на Github: https://github.com/jbarrez/activiti-in-mem-prototype
(Люди иногда на форуме спрашивали, как трудно было запускать Activiti исключительно в памяти, для простых случаев использования, которые не требуют использования базы данных. Ну, теперь это совсем не сложно! Кто знает … этот маленький прототип может стать чем-то, если людям это понравится!)
- Как и ожидалось, мы обменяем реализации DataManager с нашей версией в памяти, см. InMemoryProcessEngineConfiguration
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
@Override protected void initDataManagers() { this .deploymentDataManager = new InMemoryDeploymentDataManager( this ); this .resourceDataManager = new InMemoryResourceDataManager( this ); this .processDefinitionDataManager = new InMemoryProcessDefinitionDataManager( this ); this .jobDataManager = new InMemoryJobDataManager( this ); this .executionDataManager = new InMemoryExecutionDataManager( this ); this .historicProcessInstanceDataManager = new InMemoryHistoricProcessInstanceDataManager( this ); this .historicActivityInstanceDataManager = new InMemoryHistoricActivityInstanceDataManager( this ); this .taskDataManager = new InMemoryTaskDataManager( this ); this .historicTaskInstanceDataManager = new InMemoryHistoricTaskInstanceDataManager( this ); this .identityLinkDataManager = new InMemoryIdentityLinkDataManager( this ); this .variableInstanceDataManager = new InMemoryVariableInstanceDataManager( this ); this .eventSubscriptionDataManager = new InMemoryEventSubscriptionDataManager( this ); } |
Такие реализации DataManager довольно просты. Посмотрите, например, InMemoryTaskDataManager, которому необходимо реализовать методы извлечения / записи данных для TaskEntity:
1
2
3
4
5
6
7
8
9
|
public List<TaskEntity> findTasksByExecutionId(String executionId) { List<TaskEntity> results = new ArrayList<TaskEntity>(); for (TaskEntity taskEntity : entities.values()) { if (taskEntity.getExecutionId() != null && taskEntity.getExecutionId().equals(executionId)) { results.add(taskEntity); } } return results; } |
Чтобы доказать, что это работает, давайте развернем, запустим простой экземпляр процесса, выполним небольшой запрос задачи и проверим историю Этот код в точности совпадает с «обычным» использованием Activiti.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class Main { public static void main(String[] args) { InMemoryProcessEngineConfiguration config = new InMemoryProcessEngineConfiguration(); ProcessEngine processEngine = config.buildProcessEngine(); RepositoryService repositoryService = processEngine.getRepositoryService(); RuntimeService runtimeService = processEngine.getRuntimeService(); TaskService taskService = processEngine.getTaskService(); HistoryService historyService = processEngine.getHistoryService(); Deployment deployment = repositoryService.createDeployment().addClasspathResource( "oneTaskProcess.bpmn20.xml" ).deploy(); System.out.println( "Process deployed! Deployment id is " + deployment.getId()); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( "oneTaskProcess" ); List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list(); System.out.println( "Got " + tasks.size() + " tasks!" ); taskService.complete(tasks.get( 0 ).getId()); System.out.println( "Number of process instances = " + historyService.createHistoricProcessInstanceQuery().count()); System.out.println( "Number of active process instances = " + historyService.createHistoricProcessInstanceQuery().finished().count()); System.out.println( "Number of finished process instances = " + historyService.createHistoricProcessInstanceQuery().unfinished().count()); } } |
Что, если вы запустите, это даст вам это (невероятно быстро, поскольку все это в памяти!):
1
2
3
4
5
6
7
8
9
|
Process deployed! Deployment id is 27073df8-5d54-11e5-973b-a8206642f7c5 Got 1 tasks! Number of process instances = 1 Number of active process instances = 0 Number of finished process instances = 1 |
В этом прототипе я не добавил транзакционную семантику. Это означает, что если два пользователя одновременно выполнят одну и ту же задачу, результат будет неопределенным. Конечно, возможно иметь логику транзакций в памяти, которую вы ожидаете от Activiti API, но я еще не реализовал это. По сути, вам нужно будет хранить все объекты, к которым вы прикасаетесь, в небольшом кэше до времени сброса / фиксации и выполнять некоторую блокировку / синхронизацию в этой точке. И, конечно же, я принимаю запросы на тягу 🙂
Что дальше?
Ну, это в значительной степени зависит от вас. Дайте нам знать, что вы думаете об этом, попробуйте!
Мы находимся в тесном контакте с одним из членов / клиентов нашего сообщества, которые планируют опробовать его в ближайшее время. Но мы, конечно же, тоже хотим поиграть с этим самим, и мы смотрим на то, что было бы круто для первого выбора (у меня все еще есть особое место в моем сердце для Neo4j … что было бы прекрасно, так как это транзакционно).
Но самое важное: в Activiti v6 теперь можно чисто поменять постоянный слой. Мы очень гордимся тем, как это выглядит сейчас. И мы надеемся, что вам тоже понравится!
Ссылка: | Настойчивая настойчивость в Activiti 6 от нашего партнера JCG Джорама Барреса в блоге « Маленькие шаги с большими ногами» . |