Saturday, December 20, 2014

DDT: generic DataProvider update to allow concurrent entity modification

In a previous post we've created generic DataProvider (DP) that allows concurrent read operations from multiple data sources. In this article we'll update existing solution with a new Save feature, as in some cases we need to modify entities dynamically, e.g. precondition / post condition for reset password test.

Let's start with DAO interface updates:


As you can see, we've added a new save definition for further entities' modifications confirmation. Now let's take a look at its implementation:


The only thing I'd like to pay attention to is merge operation. If N threads try to modify the same entity concurrently, we need to make sure that it won't be completely overwritten. For example, for Users table, if 1st thread changes email and 2nd one - password concurrently, we expect to see both updates, instead of entire entity replacement by the last thread. However, if both threads try to change the same field, of course this particular value should be overwritten. To handle all these moments, we should also add a special annotation to all the entities that could be potentially modified.


DynamicUpdate annotation will help to get latest entity state before merging.

However, if you expect to see entity overwriting behavior, you can just skip DynamicUpdate annotation and use any of the following API: save / saveOrUpdate / merge.

The next question is: how to call save method within test case? DP injects only entities into test signature and save operation can be accessed only from DAO object. Let's take a look at DataProviderUtils once again.


As you can see, we already use DAO object for retrieving DB fields. So now we may want to inject it into entities. Or it's even better to do that with BaseEntity:


So now we can call save method from any entity. Let's move back to DataProviderUtils to see how to inject DAO object into BaseEntity instance.


There were made only 2 small changes in comparison with previous version:
  1. retrievedFields list became more obvious. Now it collects BaseEntity objects. We need this update to avoid further casting.
  2. DataSet was extended with updateFieldsWith API for storing DAO objects.
Let's take a look at DataSet modifications:


As you can see, we're looping through fields' list and injecting DAO object.

Now we can easily save our entities directly from test cases:


These 2 tests modify the same entity. And if we run them concurrently (assuming that default password value is "password"), we may see 2 different results:
  1. email = test.user1@email.com, password = password1.
  2. email = test.user3@email.com, password = password1.
Depending on threads' execution order, email field may be overwritten, but password will be merged independently, as only 1 thread will touch it.

That's pretty much it. You can find sources on GitHub.