cancel
Showing results for 
Search instead for 
Did you mean: 

multiple engines on single database - tenant question

btomas
Champ in-the-making
Champ in-the-making
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.ParameterHandler interceptor 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.
2 REPLIES 2

martin_grofcik
Confirmed Champ
Confirmed Champ
Hi,

* how tenancy could be applied when acquiring for async jobs?
Multitenancy was not meant to distinguish between nodes (A,B). You can implement your own job executor.

The second question -> I do not have any good proposal.

Regards
Martin

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.

2. Create my custom job entity manager
<code>
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.JobEntityManager;

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>

<bean id="activitiCustomJobEntiyManagerFactory" class="com.custom.activiti.job.CustomJobEntityManagerFactory"/>
</code>

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_ &gt; 0)
      and (RES.DUEDATE_ is null or RES.DUEDATE_ &lt;= #{parameter.dueDate, jdbcType=TIMESTAMP})
      and (RES.LOCK_OWNER_ is null or RES.LOCK_EXP_TIME_ &lt;= #{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…)?