Activiti engine A and engine B (v.5.17) are running on single database. To distinguish data content per engine tenancy is used - A has tenantId=tenant_A and B has tenantId=tenant_B. The situation similar to Shared Database Multi-tenancy but here i've got separate applications running Activiti engine on its own sharing the same database.
When deploying definitions with tenantId set on separate applications (A and B ) everything works as expected - the tenantId is set automatically by Activiti engine in all entities required. When querying for specific BPM processing data per A or B engine - i've included MyBatis org.apache.ibatis.executor.parameter.ParameterHandlerinterceptor which does its work for queries that use parameter objects of type java.util.Map or org.activiti.engine.query.Query where i can set specific tenantId value (tenant_A or tenant_B).
There is another story with async job executor. When creating jobs the tenantId values are stored into the act_ru_job table. But when acquiring jobs the tenantId is not taken into account. i.e. JobEntityManager#findNextJobsToExecute executes query defined in job.xml named selectNextJobsToExecute which uses org.activiti.engine.impl.db.ListQueryParameterObject to pass parameters to the query (Date value) and not option for tenantId here.
Questions for this particular situation: * how tenancy could be applied when acquiring for async jobs? * is there any other way without using MyBatis interceptor plugins to apply BPM related data querying per tenantId on specific Activiti engine (engine A accesses only tenant_A, engine B queries only tenant_B on single DB )? One solution - use APIs which accept tenantId, but altering all my existing code with such changes doesn't look optimum.
It doesn't look that i need providing my own JobExecutor to make different Activiti engine apps to poll their own jobs by tenantId. There were some threads already started regarding <i>"multiple engines - single database"</i> (Could multiple Activiti engines share one Repository?, Usage of more than one Activiti Engines on one database ) but i didn't find particular answer to my question. I'll describe how i made <b>polling jobs by tenantId</b> per multiple Activiti engines running on single DB:
1. Provide custom engine configuration <code> public class CustomSpringProcessEngineConfiguration extends SpringProcessEngineConfiguration {
public static final String CUSTOM_MYBATIS_MAPPING_FILE = "com/custom/activiti/query/custom-mappings.xml"; @Override protected InputStream getMyBatisXmlConfigurationSteam() { return ReflectUtil.getResourceAsStream(CUSTOM_MYBATIS_MAPPING_FILE); } } </code>
The <i>custom-mappings.xml</i> actually is a copy of Activiti <i>mappings.xml</i> with my customizations and overriding the Activiti <i>Job.xml</i> implementation.
public class CustomJobEntityManagerFactory implements SessionFactory {
@Override public Class<?> getSessionType() { return JobEntityManager.class; }
@Override public Session openSession() { return new CustomJobEntityManager(); } } </code>
provide the implementation for the custom job entity manager: <code> public class CustomJobEntityManager extends JobEntityManager {
@Override public List<JobEntity> findNextJobsToExecute(Page page) { ProcessEngineConfiguration processEngineConfig = Context.getProcessEngineConfiguration(); Map<String, Object> parameter = Maps.newHashMap(); Date now = processEngineConfig.getClock().getCurrentTime(); parameter.put("tenantId", retrieveCustomTenantId()); parameter.put("dueDate", now); return getDbSqlSession().selectList("selectNextJobsToExecute", parameter, page); }
// TODO override other queries by duedate … } </code>
3. Add custom job entity manager to custom session list in Spring process engine configuration based on my CustomSpringProcessEngineConfiguration with overriden <i>getMyBatisXmlConfigurationSteam</i> <code> <bean id="customPocessEngineConfiguration" class="com.custom.activiti.engine.CustomSpringProcessEngineConfiguration"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" /> <property name="customSessionFactories"> …. …. …. <list> <ref bean="activitiCustomJobEntiyManagerFactory"/> </list> </property>
4. create a mirror of Activiti Job.xml -> CustomJob.xml with same queries appended with tenantId in <i>WHERE</i> clause and include it in my <i>com/custom/activiti/query/custom-mappings.xml</i> specified in my overriden process engine configuration <i>CustomSpringProcessEngineConfiguration </i>: <code> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.activiti.engine.impl.persistence.entity.JobEntity">
…. ….
<select id="selectNextJobsToExecute" parameterType="org.activiti.engine.impl.db.ListQueryParameterObject" resultMap="jobResultMap"> ${limitBefore} select RES.* ${limitBetween} from ${prefix}ACT_RU_JOB RES LEFT OUTER JOIN ${prefix}ACT_RU_EXECUTION PI ON PI.ID_ = RES.PROCESS_INSTANCE_ID_ where (RES.RETRIES_ > 0) and (RES.DUEDATE_ is null or RES.DUEDATE_ <= #{parameter.dueDate, jdbcType=TIMESTAMP}) and (RES.LOCK_OWNER_ is null or RES.LOCK_EXP_TIME_ <= #{parameter.dueDate, jdbcType=TIMESTAMP}) and ( (RES.EXECUTION_ID_ is null) or (PI.SUSPENSION_STATE_ = 1) ) <include refid="tenantIdCriteria"/> ${limitAfter} </select> <sql id="tenantIdCriteria"> <if test="parameter.tenantId != null"> and RES.TENANT_ID_ = #{parameter.tenantId} </if> </sql>
…. ….
</code>
Thats it.
Couldn't get to a cleaner solution at the moment. What i don't like here - i keep a copy of <i>mappings.xml</i> and override <i>Job.xml</i>. Such implementation complicates upgrading to new Activiti versions and it may introduce new bugs.
<b>Questions:</b>
The ACT_RU_JOB contains TENANT_ID_ and it is filled when job created. Why tenantId is not used in job polling? Maybe there are some threats for which polling by tenantId was not introduced?
<i>org.activiti.engine.impl.persistence.entity.JobEntityManager</i> works with the <b>default job executor</b>. If i want the <b>new async job executor</b> to use tenantId - would it be enough to override <i>org.activiti.engine.impl.persistence.entity.data.impl.MybatisJobDataManager</i> the same way as the <i>JobEntityManager</i>?
Is there any other solution to make polling jobs by tenantId without copying <i>Job.xml</i> as described above?
Would JobEntityManager/MybatisJobDataManager cover all job executions polling that occur in BPMN asynchronously (timers, parallel gateway, async…)?