Now that we have functionality that allows clients to submit books for publishing by placing them in the
BookPublishRequestManager, we need to implement the functionality that actually processes the requests to get some
books published!
Milestone 1: Implement BookPublishTask
Currently, the only way that any code in our service gets executed is via a request from a client. However, we don’t
want to require our clients to make another request to execute the publishing of their book! So how do we get our
books published? We can accomplish this by starting a new thread to execute a Runnable that publishes a book. To
simplify things a bit for you, we have provided logic that will start a thread and regularly execute a Runnable.
You will only be responsible for implementing the Runnable class that contains the publishing logic.
The BookPublisher we have provided is responsible for scheduling the execution of the Runnable repeatedly. If you
open up the BookPublisher class and take a look a the start method, you’ll see that a Runnable is being scheduled
to execute every second. You don’t need to call the start method, it is called when your service starts
up and currently is executing the NoOpTask that was also provided to you.
You are responsible for writing a new class BookPublishTask, that implements Runnable and processes a publish
request from the BookPublishRequestManager. If the BookPublishRequestManager has no publishing requests the
BookPublishTask should return immediately without taking action. You will also need to update CatalogDao with new
methods for the BookPublishTask to publish new books to our Kindle catalog.
Refer to the 'Asynchronous Book Publishing' section of the design doc for BookPublishTask's sequence diagram and
implementation notes. Take special care to consider what happens and what steps to take if an exception is thrown in
BookPublishTask's run method!
In order to switch the BookPublisher to start scheduling your new BookPublishTask instead of the NoOpTask, you
will need to update the Dagger code that passes a NoOpTask to the BookPublisher constructor. Once you’ve made this
switch, you can delete NoOpTask and its test class.
To test, submit a book publish request by calling SubmitBookForPublishing. You should then get back a
publishingRecordId, which you’ll use to check the status by calling GetPublishingStatus. You should be able to see
the different states that the request has gone through in the publish status history. Once you see that the publish
request has hit the SUCCESSFUL state, you can call GetBook with the bookId to see the new or updated book!
Note: there is a limitation to our current implementation that will affect your testing. We are maintaining all
publishing requests in memory in the BookPublishRequestManager, which means that when you restart your service any
requests that were in the BookPublishRequestManager previously are lost forever. This is not ideal, but we’ve kept
things simple for the sake of this project. To remove this limitation, we could use different technologies such as
SQS or persisting the requests in a datastore like DynamoDB, but that’s out of scope for
this project.
Run MasteryTaskFourSubmitBookForPublishingTests to validate your changes.
Milestone 2: Make BookPublishRequestQueue and BookPublishRequest thread safe
We now have a working BookPublishTask running which processes our BookPublishRequests! Each request from a client
is processed in its own thread, therefore we have multiple threads writing to the
Queue in our BookPublishRequestManager, and another thread reading from it in order to publish. Whenever multiple
threads are accessing the same resource, you can have big trouble! You will want to think of ways to ensure that only
one thread at a time is writing to or reading from the shared resource. This will ensure that BookPublishRequests
added to the Queue are in the correct order, and BookPublishRequests are removed and processed in the correct order.
We need to update BookPublishRequestManager to be thread-safe so that it behaves as expected
even if multiple threads are accessing it. Sometimes you will have to write extra code to ensure thread-safety, but
Java also provides thread-safe implementations of data structures you can use. Java provides a thread-safe queue called
ConcurrentLinkedQueue which we can use instead of a LinkedList. Like LinkedList, it also implements the Queue
interface.
The ConcurrentLinkedQueue will ensure that when multiple threads are writing to the queue, the BookPublishRequests
will be added in the correct order, and that reads from the queue will then access BookPublishRequests in the
correct order. Update your service so that BookPublishRequestManager uses a ConcurrentLinkedQueue instead
of a LinkedList.
Optional: You can browse ConcurrentLinkedQueue’s documentation
for more information about its implementation.
Dealing with multiple threads accessing a shared resource is only an issue when multiple threads can change the state of the resource. If the shared resource can’t change, we don’t need to be concerned that one thread might not know about another thread’s write. Therefore another tool we have to write thread-safe code is to make the shared resource immutable.
A BookPublishRequest will never be a resource shared by multiple threads, but let's practice using our immutability
tool anyway. Update BookPublishRequest be immutable, and by doing so we'll prevent unintended subclassing!
Exit checklist:
- You’ve implemented
BookPublishTask, aRunnablewhich processes a request from theBookPublishRequestMangerto publish a books to the catalog. - You’ve updated
BookPublishRequestMangerandBookPublishRequestto make them thread safe. - You’ve added unit tests to cover your new code.
MasteryTaskFourSubmitBookForPublishingTestspass