[ { "id": "playlist::00011b74-12be-4e60-abbf-b1c8b9b40bfe" }, { "id": "playlist::0f1ef827-def8-4ba8-ba1a-84289c26942b" }, { "id": "playlist::3a62603f-9171-4ebc-a37d-74f6f891d1b1" }, { "id": "playlist::d33f19c8-6d6f-48c3-a2e2-0fa7cef9a0b2" }, { "id": "playlist::d5cb2092-37db-499c-9b18-66703f298ce4" } ]
Implementing Solutions for Production-ready Concerns
Introduction
In this lab you will begin to prepare the Couchify application for production by implementing several enhancements. These enhancements are designed to address a number of concerns, including the need to only update portions of a document and support for concurrent access to documents and the potential for update collisions.
What you will gain experience with:
-
Performing sub-document fetches and updates
-
Implementing Optimistic locking
Estimated time to complete: 30 minutes
Instructions
You will be performing work on the lab-06
project.
Be sure to close all other projects and open this project.
Implementing a Sub-Document Mutation solution
Imagine a scenario where the size of the document is large enough that it is not feasible to fetch, update and replace the document. Consequently, you may prefer instead to only update a portion of the document.
Use case
In the Couchify application, the current data model used is based on the couchmusic2 data set. This data set incorporates the partial embedding of userprofile data in the playlist documents. This is so that it is easier to pre-load playlists and have the basic user information associated with the playlist without having to also fetch the associated userprofile document. However, if someone were to potentially update portions of their user profile, as in the case where someone chooses to change their last name or update their user images, this would ultimately need to be replicated to all playlists for the user. While there are a number of ways this could be done, one simple way of propagating the updates is to extract the necessary owner information and then update just portion of each of the related playlist documents.
Implementation
In order to support this implementation, a number of features need to be implemented. Take a moment to open the PlaylistRepository.cs file and locate the UpdatePlaylistOwnerForUser() method. This high-level method has been implemented for you already. In summary, this method performs the following tasks.
-
Obtains a list of playlists for the given username
-
Obtains the UserProfile domain object for the given username
-
Uses a convenience method on the UserProfile class to populate the Owner object with the relevant information.
-
Iterates through the list of Playlist IDs, altering the owner attribute of each via the call to UpdatePlaylistOwner(), which is the method that implements the sub-document update.
You will need to implement the functionality of the various methods called here.
-
Returning a list of Playlist IDs (keys) for the given username)
Define a N1QL query to return a list of Playlist IDs for the given username. This is very similar to the query to return a list of Playlists you implemented in the FindPlaylistsByUsername() method. The key difference is that you will be returning the IDs (keys). Recall that there is a special operator for the query where you can request the document metadata - meta(). First inspect the entire set of metadata, but you are particularly interested in the id attribute. When you initially implement the query, you may find you get results as follows
You will need to make one slight adjustment to the query. If you add the qualifier value in front of the item being selected as
select value meta().id
, you should get a result more similar to the following.[ "playlist::00011b74-12be-4e60-abbf-b1c8b9b40bfe", "playlist::0f1ef827-def8-4ba8-ba1a-84289c26942b", "playlist::3a62603f-9171-4ebc-a37d-74f6f891d1b1", "playlist::d33f19c8-6d6f-48c3-a2e2-0fa7cef9a0b2", "playlist::d5cb2092-37db-499c-9b18-66703f298ce4" ]
This will allow you to retrieve the results as a List<String>.
Locate the FindPlaylistIdsByUsername() method in the PlaylistRepository.cs class and begin implementing the query functionality to query for and return a List<string> containing the list of Playlyst IDs for the given username.
Verify correct behavior by running the FindPlaylistIdsForUsername_Success() test method found in the Lab07Tests test class.
-
Return a UserProfile for a given username
Design a query designed to retrieve all UserProfile documents having a specified username. A good test case would be to find the UserProfile document for username: stockadeseffusing18695. Use the Query Editor to test this query. Additionally, you will need to create a Secondary Index or Composite Index to support the query you defined.
Open the UserProfileRepository.cs class and locate the empty FindUserProfileByUsername() method. Implement the functionality by using the N1QL query you defined and tested in the Query Editor. Execute the query with the provided query string and mapping the provided username as a query parameter. Note that this is expected to only return one item, but results will be returned as a List. Process the results, assuming that there should only be one UserProfile returned.
Verify the correct implementation by running the Find_UserProfile_By_Username_Success() method found in the Lab07Tests.cs test class.
-
Implement method to update the owner attribute of the Playlist
Finally, implement the logic of the UpdatePlaylistOwner() method of the PlaylistRepository.cs file. This method should utilize the sub-document update discussed in the course material using the MutateInAsync() method on the Collection class. The Owner class has been passed into the method and can be provided directly to the appropriate sub-document mutation method. Just as you saw earlier during the Key/Value operations, Couchbase will attempt to perform serialization, converting the Owner object into its appropriate representation to store in the document. Be sure to also include the appropriate exception handling code to address any potential errors that could arise.
Verify correct implementation by running the SubDocument_Update_Success() test method in the Lab07Tests test class.
Implementing an Optimistic Locking solution
Imagine that as you begin to make the Couchify application available more widely that it is now conceivable that more than one user may be attempting to make a change on a particular document from within their application. It is important to consider and adopt an appropriate locking strategy where the integrity of the updates are important.
Use case
In the Couchify application, one area where it may be critical to ensure update integrity is in the case where a user decides to change their password. While in most cases, it would be safe to assume that this would only be done from one application instance by one user, there is nothing in the application functionality that would prevent multiple updates from happing from two different instances at the same time. The goal of this section of the lab is to create an Optimistic Locking solution specifically for a case where the user intends to update their password.
Implementation
Open the UserProfileRepository.cs class and locate the UpdatePasswordWithOptimisticLock() method. Notice the method is providing the key, used to find the UserProfile instance and the new password. The core logic for this method will be as follows.
-
Get the UserProfile object for the given key
-
Update the password property of the UserProfile object with the password provided
-
Save the updated UserProfile object using the appropriate update method
To ensure that this series of steps occurs safely, be sure to implement the Optimistic Locking capability using the retry pattern discussed in the course material.
Some key principles to keep in mind are:
-
Allow a maximum of 5 retries
-
Recall that Optimistic Locking is implemented by providing the original CAS value as a method call to the appropriate Options class passed into the update.
-
If an error does occur due to the CAS value not matching, it is appropriate to wait a second or so before retrying. The example code in the course used the static Sleep method from the System.Thread class.
-
The example in the course material used a break statement to break out of the loop when the update succeeds. Given the method expects a return value of the UserProfile object, you can just return this value on successful update. If the loop completes without succeeding, this should be considered an error and you should throw a RepositoryException with an appropriate message indicating the update failed.
With these principles in mind, implement the method. When complete, verify functionality by running the OptimisticLock_Success() test in the Lab07Tests test class. Given the difficulty of verifying Optimistic Locking with two concurrent users, the test will only verify that the correct core logic was implemented.
Lab Summary
In this lab, you had a chance to implement solutions designed to ready your Couchify application for production. These solutions included addressing being able to retrieve and update sub-documents in circumstances where the document may be large enough to warrant the need to not retrieve and update the entire document. In addition, you had a chance to implement an Optimistic Locking solution that ensures you can safely update critical attributes of a document, such as the user password. These solutions and others you may implement are key steps you will take to ready your application to be run in a production environment comprised of many of the following characteristics.
-
Larger documents may require performance optimizing solutions to minimize the traffic of reading and writing these large documents.
-
Recognizing that multiple users will be using the application and potentially updating the same document at the same time, it is necessary proactively take steps to ensure that key data in the application is protected via locking strategies
-
In a production Couchbase cluster, it becomes critical to not only distribute the data across multiple servers to enable scalability as the application data set grows, but it is also critical to ensure fault-tolerance by maintaining replicas and persisting to disk. In this kind of environment, it may be necessary specify what should happen on mutation operations where these two factors are in play to ensure that data is written to a majority of copies and/or a majority of disks before completing the mutation operation.
Congratulations! You have completed this lab.