Describing how the job position is requested
In the first part, we find all the answers to our questions; however, a few remain unanswered:
- How is the first part of the process represented? How can we track when the new job position is discovered, the request for that job position is created, and when this job position is fulfilled?
- Why can't we add more activities to the current defined process? What happens when we add the create request, find a candidate, and job position fulfilled activities inside the interview process?
The answers for these questions are simple. We cannot add these proposed nodes to the same process definition, because the interview process needs to be carried out (needs to be instantiated) once for each candidate that the recruiting team finds. Basically, we need to decouple all these activities into two processes. As the MyIT Inc. manager said, the relationship between these activities is that a job request will be associated with the N-interviews' process.
The other important thing to understand here, is that both the processes can be decoupled without using a parent/child relationship. In this case, we need to create a new interview's process instance when a new candidate is found. In other words, we don't know how many interviews' process instances are created when the request is created. Therefore, we need to be able to make these creations dynamically.
We will introduce a new process that will define these new activities. We need to have a separate concept that will create an on-demand new candidate interviews' process, based on the number of candidates found by the human resources team. This new process will be called "Request Job Position" and will include the following activities:
- Create job request: Different project leaders can create different job requests based on their needs. Each time that a project leader needs to hire a new employee, a new instance of this process will be created where the first activity of this process is the creation of the request.
- Finding a candidate: This activity will cover the phase when the research starts. Each time the human resources team finds a new candidate inside this activity, they will create a new instance of the candidate interviews' process. When an instance of the candidate interviews' process finds a candidate who fulfills all the requirements for that job position, all the remaining interviews need to be aborted.
We can see the two process relationships in the following figure:
If we express the Request Job Position process in jPDL, we will obtain something like this:
In the following section, we will see two different environments in which we can run our process. We need to understand the differences between them in order to be able to know how the process will behave in the runtime stage.
Based on the way we choose to embed the framework in our application, it's the configuration that we need. We have three main possibilities:
- Standalone applications
- Web applications
- Enterprise application
Standalone application with jBPM embedded
In Java Standard Edition (J2SE) applications, we can embed jBPM and connect it directly to a database in order to store our processes. This scenario will look like the following image:
In this case, we need to include the jBPMJARs in our application classpath in order to work. This is because our application will use the jBPM directly in our classes.
In this scenario, the end users will interact with a desktop application that includes the jbpm-jpdl.jar file. This will also mean that in the development process, the developers will need to know the jBPM APIs in order to interact with different business processes.
It's important for you to know that the configuration files, such as hibernate.cfg.xml and jbpm.cfg.xml will be configured to access the database with a direct JDBC connection.
Web application with jBPM dependency
This option varies, depending on whether your application will run on an application server or just inside a servlet container. This scenario will look like:
In this case, we can choose whether our application will include the jBPMJARs inside it, or whether the container will have these libraries. But once again, our application will use the jBPM APIs directly.
In this scenario, the end user will interact with the process using a web page that will be configured to access a database by using a JDBC driver directly or between a DataSource configuration.
Running the recruiting example
In this section, we will cover the first configuration (the standalone one). This configuration can also be used to develop a web application. We will see that one has to test the whole process, which has been recently executed in order to see how it behaves.
This process will live only in memory, and when the thread that starts it dies, all the changes in that process will be lost. In other words, this process will start and end in the same thread, without using the database access to store the process status.
Running our process without using any services
In this section, we will see how our two processes will run using JUnit, so that we can test their behavior. The idea is to know how to move the process from one state to the other, and also to see what is really going on inside the framework.
Feel free to debug the source code provided here step by step, and also to step into jBPM code to see how the jBPM classes interact in order to guide the company's activities.
In this test, we will see how our two processes are chained logically in order to simulate the real situation. By "logically", I mean that the two processes are manually instantiated when they are needed. It is important to notice this, because there are situations where the process can be automatically instantiated, which is not the case here.
Take a look at the project called /RecruitingProcess/. You will find both the process definitions under /src/main/resources/jpdl. If you open the test called RecruitingProcessWithOutServicesTestCase located inside /src/test/org/jbpm/example/recruiting/, you will see a long test that shows how the process behaves in a normal situation.
Here we will explain this execution in order to understand the expected behavior and how this can be checked using JUnit asserts.
Normal flow test
If you take a look at the method called test_NormalFlowWithOneCandidate() inside the test case, you will see that we are trying to execute and test the normal flow of our defined process. We will simulate the situation where a new job position request is created. Then in our test, a new candidate is found. This will mean that a new candidate interview process will be created to evaluate if the candidate will get the job or not.
This is a simple but large test, because the process has a lot of activities. I suggest you to take a look at the code and follow the comments inside it.
In a few lines, you will see the following behavior:
- A new job position request is created. This will happen when a project leader requires a new team member. This will be translated to an instance of the Request Job Position process. Basically, we parse the jPDL XML definition to obtain a ProcessDefinition object and then create a new ProcessInstance from it.
- Now we need to start this process. When we start this process, the first activity is to create the request. This means that someone needs to define the requisites that the job position requires. These requisites will then be matched with the candidate's resume to know if he/she has the required skills. The requests (requisites) are created automatically to simulate the developer job position. This is done inside the node-enter event of the "Create Request" activity. You can take a look at the source code of the CreateNewJobPositionRequestActionHandler class where all this magic occurs.
- When this request is created, we need to continue the process to the next activity. The next activity is called "Find Candidate". This activity will be in charge of creating a new process instance for each candidate found by the human resources team. In the test, you will see that a new candidate is created and then a new instance of the Candidate Interviews process is created. Also, in the test, some parameters/variables are initialized before we start the process that we created. This is a common practice. You will have a lot of situations like this one where you need to start a process, but before you can start it, some variables need to be initialized. In this case, we set the following three variables:
- REQUEST_TO_FULFILL: This variable will contain a reference to the process instance that was created to request a new job position.
- REQUEST_INFO: This variable will contain all the information that defines the job request. For example, this will contain the profile that the candidate resume needs to fulfill in order to approve the first interview.
- CANDIDATE_INFO: This variable will contain all the candidate information needed by the process.
- Once the variables are set with the correct information, the process can be started. When the process is started, it stops in the "Initial Interview" activity. In this activity, some data needs to be collected by the recruiting team, and this again is simulated inside an action handler called CollectCandidateDataActionHandler. In this action handler, you will see that some information is added to the candidate object, which is stored in the CANDIDATE_INFO process variable.
- The information stored in the CANDIDATE_INFO process variable is analyzed by the following node called "Initial Interview Passed?" that uses a decision handler (called ApproveCandidateSkillsDecisionHandler) to decide whether the candidate will go to the next activity or he/she will be discarded.
- The same behavior is applied to the "Technical Interview" and "Technical Interview Passed?" activities.
- Once the technical interview is approved, the process goes directly to the "Medical Checkups" stage where a fork node will split the path of execution into three. At this point, three child tokens are created. We need to get each of these tokens and signal them to end each activity.
- When all the medical examinations are completed, the join node will propagate the execution to the next decision node (called "Medical Exams passed?"), which will evaluate whether the three medical check ups are completed successfully.
- If the medical exam evaluation indicates that the candidate is suitable for the job, the process continues to the last stage. It goes directly to the Project leader Interview, where it will be decided whether the candidate is hired or not. The outcome of this interview is stored inside a process variable called PROJECT_LEADER_INTERVIEW_OK inside the ProjectLeaderInterviewActionHandler action handler. That process variable is evaluated by the decision handler (FinalAcceptanceDecisionHandler) placed inside the "Final Acceptance?" activity.
- If the outcome of the "Final Acceptance?" node is positive, then an automatic activity is executed. This node called "Create WorkStation" will execute an automatic activity, which will create the user in all the company systems. It will generate a password for that user and finally, create the user's e-mail account. It will then continue the execution to the "Candidate Accepted" end state.
- In the "Candidate Accepted" node, an action is executed to notify that the job position is fulfilled. Basically, we end the other process using the reference of the process stored in the variable called REQUEST_TO_FULFILL.
I strongly recommend that you open the project and debug all the tests to see exactly what happens during the process execution. This will increase your understanding about how the framework behaves. Feel free to add more candidates to the situation and more job requests to see what happens.
In this article, we saw a full test that runs two processes' definitions, which are created based on real requisites. The important points covered in this article are:
- How to understand real-life processes and transform them into formal descriptions
- How this formal description behaves inside the framework
- How to test our process definitions