One of the primary challenges of distributed caching is managing data relationships. For example, it is often unclear to users how to map and store relational data in a cache, given that the data has no relational model and is stored as a key-value pair. Further, most applications employing caching are heavily dependent on database calls, how does data stay up-to-date on both fronts? It is to address such concerns that NCache provides users with its Data and Cache Dependencies (i.e., Database, and External) Features. In this blog, we’ll discuss these NCache Data Dependencies pertaining to Scala applications.
What are Data Dependencies & Cache Dependencies?
Data dependencies are when a relationship is created among specified cached data so that one is updated according to the other or vice versa, i.e., a dependency relationship. For example, if a cache contains data about a product and the orders placed for it – the latter would be unnecessary if the organization in question shelves this product.
On the other hand, cache dependencies are when the data or data sets in the cache are dependent on an external source, such as SQL, Oracle, or OleDB. Alternatively, these sources could also be files in other locations. The aim of cache dependencies is not to keep data across the cache consistent (like with data dependencies) but to keep data between the cache and the external source synchronized.
NCache offers users several varieties for both of these types of dependencies. However, we will only be discussing the Scala implementation of a few. Please refer to our programmer’s guide for further details on these varieties.
Implementing Data Dependency
The most commonly used data dependency is the Key Dependency. This term refers to when a data dependency is created using the keys used to identify cache items, i.e., keys are used to maintain the data dependency relationship. Essentially, dependent items are removed based on modifications in the master key, namely updates, and deletes. Key Dependencies can be of two types; Multi-level or Multiple. The difference between these two types is demonstrated in the diagram below using the example discussed earlier.

Figure: Multi-level and Multiple dependencies, respectively.
These diagrams demonstrate the chain of events that will occur in case of a modification in either case. For instance, in both cases, if the Product is deleted, the Order and the OrderDetails are also deleted. You can add a CacheItem with a Key Dependency in Scala as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Get customer from database against given customerId val customer = fetchCustomerFromDb("ALFKI") // Create a unique cache key for this customer val customerKey = "Customer:" + customer.getCustomerId // Get orders against the customer from the cache val order = fetchOrderByCustomerId("ALFKI") // Generate a unique key for order val orderKey = "Order:" + order.getOrderId // Create a new cache item for this order val cacheItem = CacheItem(order) // Create key dependency cacheItem.setDependency(KeyDependency(customerKey)) // Insert cache item with key dependency cache.insert(orderKey, cacheItem) // For successful addition of cacheItem with dependency // update the key and check if cacheItem is present in the cache |
Implementing Cache Dependency
The first cache dependency we’ll be discussing is the SQL Cache Dependency. When an item is added with this dependency, NCache establishes a link with the database against the query result set. So, in the event of a data update, the SQL Server in question fires event notifications that NCache abides by when caching and removing data. You can add data to a cache with this dependency using the NCache Scala API, as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// Creating a connection string to establish connection with the database val connString = "YourConnString" val customerId = "ALFKI" // Creating the query which selects the data on which cache data is dependent val query = "Select CustomerID, Address, City FROM dbo.Customers WHERE CustomerID = ?" val queryCommand = new QueryCommand(query) queryCommand.setParameters(Map("CustomerID" -> customerId)) // Creating SQL dependency val sqlDependency = new SqlCacheDependency(connString, query) // Get orders against customerId val orders = fetchOrdersByCustomerId(customerId) for (order <- orders) { // Generate a unique key for this order val key = "Order:" + order.getOrderId // Create a new cache item and add SQL dependency to it val cacheItem = CacheItem(order) cacheItem.setDependency(sqlDependency) // Add cache item in the cache with SQL dependency cache.insert(key, cacheItem) // For successful addition of item with SQL Dependency // Update the record in the database and check if key is present |
As previously discussed, cache data can also depend on other external sources like files. These File Dependencies work based on the same principle of updating data based on event notifications and can be implemented as identified below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Generate a unique key for the fileData val key = "FileData" // Get the content of the file as string var fileData = cache.get(key, classOf[String]) // Check if the fileData is already in the cache if (fileData != null) { // Read the contents of the file placed at the path var file = new File("test.txt") try { var buffer = BufferedReader(FileReader(file)) buffer.readLine() } catch { case exception: Exception => // Handle exceptions } } // Specify the file path to add dependency on the file val filePath = "test.txt" // Create a new cache item with the file data var cacheItem = CacheItem(fileData) // Create file dependency on the file placed at filePath cacheItem.setDependency(FileDependency(filePath)) // Add the file data in the cache with dependency on the file cache.insert(key, cacheItem) |
Conclusion
Clearly, dependencies make using cache data and particularly managing its relationships a much simpler process. Thus, there is added incentive for people using the sort of high-traffic, data-focused applications common with Scala. Although, of course, these dependencies have universal appeal, and if you want to read more about data and cache dependencies in .NET, you can read our blogs on the topic.