{"id":6373,"date":"2024-08-21T12:52:46","date_gmt":"2024-08-21T12:52:46","guid":{"rendered":"https:\/\/www.solutionstreet.com\/blog\/?p=6373"},"modified":"2024-08-21T12:57:28","modified_gmt":"2024-08-21T12:57:28","slug":"spring-declarative-caching","status":"publish","type":"post","link":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/","title":{"rendered":"Spring Declarative Caching"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"765\" height=\"438\" src=\"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png\" alt=\"\" class=\"wp-image-6417\" style=\"width:632px;height:auto\" srcset=\"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png 765w, https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title-300x172.png 300w\" sizes=\"auto, (max-width: 765px) 100vw, 765px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I had occasion at work recently to consider using Spring\u2019s&nbsp;<code>@Cacheable<\/code>&nbsp;annotation. I searched the web for some hints on how to use it, but wasn\u2019t satisfied with any of the results. Even my favorite source<sup data-fn=\"a5329585-69f8-4b9f-8c58-57b3d7f99bf0\" class=\"fn\"><a id=\"a5329585-69f8-4b9f-8c58-57b3d7f99bf0-link\" href=\"#a5329585-69f8-4b9f-8c58-57b3d7f99bf0\">1<\/a><\/sup>&nbsp;of bite-size Java code samples had examples that I felt were&nbsp;<a href=\"https:\/\/www.baeldung.com\/spring-cache-tutorial#2-cacheevict\" target=\"blank\" rel=\"noopener\">too<\/a>&nbsp;<a href=\"https:\/\/www.baeldung.com\/spring-cache-tutorial#3-cacheput\" target=\"blank\" rel=\"noopener\">simple<\/a>. Using \u201clookup\u201d methods as examples of places where you may want to put <code>@CacheEvict<\/code> and <code>@CachePut<\/code> annotations seemed misleading to me.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So, at the very least, I wanted to provide more substantial examples. In particular, I wanted to show why Spring\u2019s default cache implementation requires careful thought in a distributed application environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the process, I ended up going off the rails a bit. I spent way more time setting up an environment in which I could run my examples than I did writing actual Java code. But, in a way, that\u2019s great! If you came here to read Java code, the good new is: there\u2019s not a lot of it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sample-code\">Code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For this blog post, I bootstrapped a Spring project using the&nbsp;<a href=\"https:\/\/start.spring.io\/\" target=\"blank\" rel=\"noopener\">spring initializr<\/a>&nbsp;and then&nbsp;<a href=\"https:\/\/github.com\/emacdona\/blog-spring-cacheable\" target=\"blank\" rel=\"noopener\">put it on GitHub<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To run the examples with the least amount of fuss, you\u2019ll need&nbsp;<code>Docker<\/code>,&nbsp;<code>Docker Compose<\/code>, and (gnu)&nbsp;<code>make<\/code>, <code>curl<\/code>, and <code>jq<\/code>. If you have those tools, things&nbsp;<em>should<\/em>&nbsp;go smoothly. However, it&nbsp;<em>is<\/em>&nbsp;software\u2026 so, you know\u2026 good luck. If it breaks, you\u2019ve got the source code!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For examples that show a command typed at a prompt, this character is my prompt:&nbsp;<code>\u2717<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"big-picture\">Big Picture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Broadly speaking, you\u2019d like to cache return values of methods that are expensive to compute. You can measure \u201cexpensive\u201d in different ways (e.g., CPU or memory used); but often, when we say \u201cexpensive,\u201d we mean \u201cit takes too much time.\u201d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If we assume that all methods in question are \u201cfunctions\u201d in the mathematical sense \u2013 i.e., given the same inputs (to include the instance the method is operating on), they always give the same output \u2013 then caching is pretty simple. Any return value of such a method can be cached indefinitely, and the only decisions you need to make with respect to your cache are:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>How big can it get?<\/li>\n\n\n\n<li>Which eviction policy do you choose to ensure the size constraint is maintained?<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">If we do&nbsp;<strong><em>not<\/em><\/strong>&nbsp;assume that the method in question are functions, then we can consider much more interesting examples.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For instance, consider a service that, given an identifier, retrieves a record from a relational database. Assume that this database has multiple clients, each of which can update records. Should you expect that, given the same identifier, the service should return the same record now that it did six hours ago? Of course not; the record could have been updated in the intervening time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sample-application\">Sample Application<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The sample application includes a very simple service for managing a database of books. It uses Spring declarative caching to minimize database lookups. It lets you:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Retrieve books<\/li>\n\n\n\n<li>Change books\u2019 titles<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Following is a stripped down version of the RestController class that implements the API endpoints for our service (see the source code for the complete class).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>@RequestMapping(\"\/books\")\n<strong>public<\/strong> <strong>class<\/strong> BookRestController {\n  <strong>private<\/strong> <strong>final<\/strong> BookRepository bookRepository;\n\n  @GetMapping(\"\/clear\")\n  @CacheEvict(cacheNames = \"books\", allEntries = <strong>true<\/strong>)\n  <strong>public<\/strong> <strong>void<\/strong> clearCache() {\n  }\n\n  @GetMapping\n  <strong>public<\/strong> Collection&lt;Book&gt; books() {\n    <strong>return<\/strong> bookRepository.findAll();\n  }\n\n  @GetMapping(\"\/{isbn}\")\n  @Cacheable(cacheNames = \"books\")\n  <strong>public<\/strong> Book bookByIsbn(@PathVariable(\"isbn\") String isbn) {\n    <strong>return<\/strong> bookRepository.findByIsbn(isbn);\n  }\n\n  @GetMapping(\"\/{isbn}\/badUpdateTitle\/{title}\")\n  <strong>public<\/strong> Book badUpdateTitle(@PathVariable(\"isbn\") String isbn,\n                             @PathVariable(\"title\") String title) {\n    <strong>return<\/strong> bookRepository.save(bookRepository.findByIsbn(isbn).withTitle(title));\n  }\n\n  @GetMapping(\"\/{isbn}\/betterUpdateTitle\/{title}\")\n  @CacheEvict(cacheNames = \"books\", key = \"#isbn\")\n  <strong>public<\/strong> Book betterUpdateTitle(@PathVariable(\"isbn\") String isbn,\n                                @PathVariable(\"title\") String title) {\n    <strong>return<\/strong> bookRepository.save(bookRepository.findByIsbn(isbn).withTitle(title));\n  }\n\n  @GetMapping(\"\/{isbn}\/bestUpdateTitle\/{title}\")\n  @CachePut(cacheNames = \"books\", key = \"#isbn\")\n  <strong>public<\/strong> Book bestUpdateTitle(@PathVariable(\"isbn\") String isbn,\n                              @PathVariable(\"title\") String title) {\n    <strong>return<\/strong> bookRepository.save(bookRepository.findByIsbn(isbn).withTitle(title));\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Some notes on the methods and their use of Spring\u2019s declarative caching.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>clearCache<\/code>: This method is for testing purposes only! It uses&nbsp;<code>@CacheEvict<\/code>&nbsp;to clear&nbsp;<strong><em>ALL<\/em><\/strong>&nbsp;items from the cache. It allows us to start from a known state (empty cache) when we are testing. In a production application, however, I can\u2019t imagine a scenario when you\u2019d want to clear the entire cache. This is one of the reasons I didn\u2019t like examples I found on the web: this seemed to be the most popular example use of this annotation.<\/li>\n\n\n\n<li><code>books<\/code>: This method returns a list of all Books. It does not cache results. It\u2019s a convenience method that lets us see all Books. There are interesting questions here that I chose to completely ignore (but they are worth further research):\n<ul class=\"wp-block-list\">\n<li>If we were to cache the result, would it cache the list as a unit, or the items in the list individually?<\/li>\n\n\n\n<li>If it does cache the list as a unit (which I think is the case), how could we make it cache the results individually?<\/li>\n\n\n\n<li>If the list were cached as a unit, how would we manage the cache when an individual Book was updated? Would we clear&nbsp;<strong><em>ALL<\/em><\/strong>&nbsp;cached lists of books (in case it appears in one of them)?<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>bookByIsbn<\/code>: Given an isbn, this method returns a single Book and caches the result (<code>@Cacheable<\/code>).<\/li>\n\n\n\n<li><code>badUpdateTitle<\/code>: Given an isbn and a title, this method will update the title of the Book determined by the isbn. This is \u201cbad\u201d because it allows for records in the database to be updated without correpsonding records in the cache being updated.<\/li>\n\n\n\n<li><code>betterUpdateTitle<\/code>: Given an isbn and a title, this method will update the the title of the Book determined by the isbn&nbsp;<em>and<\/em>&nbsp;<em>evict<\/em>&nbsp;any instance of the book from the cache (<code>@CacheEvict<\/code>). This is \u201cbetter\u201d because when a database record is updated, any corresponding record in the cache is removed.<\/li>\n\n\n\n<li><code>bestUpdateTitle<\/code>: Given an isbn and a title, this method will update the title of the book determined by the isbn&nbsp;<em>and<\/em>&nbsp;<em>update<\/em>&nbsp;any instance of the book from the cache (<code>@CachePut<\/code>). This is \u201cbest\u201d because when a database record is updated, any corresponding record in the cache is also updated.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">A stripped down version of the Book entity follows (see the source for the complete class).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>public<\/strong> <strong>class<\/strong> Book {\n  <strong>private<\/strong> String isbn;\n  <strong>private<\/strong> String title;\n  <strong>private<\/strong> String author;\n  <strong>private<\/strong> Boolean cached;\n  <strong>private<\/strong> String host;\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This entity captures the&nbsp;<code>isbn<\/code>,&nbsp;<code>title<\/code>, and&nbsp;<code>author<\/code>&nbsp;of the book \u2013 no surprises there.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it also has two additional fields:&nbsp;<code>cached<\/code>&nbsp;and&nbsp;<code>host<\/code>. These two fields aren\u2019t actually stored in the database. They are managed by some clever AspectJ code. For a given instance of a book, they allow us to see:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Whether it came from a cache.<\/li>\n\n\n\n<li>Which host provided it in response to our request (which is interesting when we deploy multiple instances of our application).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The same sample application can be deployed multiple ways, each having an impact on caching behavior.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"single-jvm\">Single JVM<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest of these architectures is a single instance of the application, using both an in-memory cache and an in-memory database (all in the same JVM):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/mermaid.ink\/svg\/eyJjb2RlIjoiYmxvY2stYmV0YVxuY29sdW1ucyA0XG5jbGllbnRbXCJjbGllbnRcIl1cbnNwYWNlXG5ibG9jazpncm91cDE6MlxuY29sdW1ucyA2XG5zcGFjZSBhcHAoW1wiYXBwXCJdKSBzcGFjZSBjYWNoZVsoXCJjYWNoZVxcbihpbiBtZW0pXCIpXSBzcGFjZSBkYlsoXCJkYlxcbihIMilcIildXG5hcHAgLS0-IGNhY2hlXG5jYWNoZSAtLT4gZGJcbmVuZFxuY2xpZW50IC0tPiBhcHAiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9fQ\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In this configuration, all requests are answered by the same application instance. That instance has a single backend database. Its responses are saved in a single cache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The following sequence diagram shows what happens when we attempt to retrieve a book. The Cache Interceptor intercepts the request and first checks to see if the result is already in the cache. If it is, the interceptor returns the result. If it\u2019s not, the interceptor calls the Endpoint Method and adds the result to the cache before returning it to the Client.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/mermaid.ink\/svg\/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG5wYXJ0aWNpcGFudCBCIGFzIENsaWVudFxuYm94IHJnYmEoOTgsIDE3NSwgMTkyLCAwLjUpIFNpbmdsZSBKVk1cbnBhcnRpY2lwYW50IENJIGFzIENhY2hlIEludGVyY2VwdG9yXG5wYXJ0aWNpcGFudCBFTSBhcyBFbmRwb2ludCBNZXRob2RcbnBhcnRpY2lwYW50IEMgYXMgQ2FjaGVcbnBhcnRpY2lwYW50IERCIGFzIERhdGFiYXNlXG5lbmRcbkItPj5DSTogR0VUXG5DSS0-PkM6IEdFVFxuYWx0IGNhY2hlIG1pc3NcbkNJLT4-RU06IEdFVFxuRU0tPj5EQjpTRUxFQ1RcbkRCLS0-PkVNOiByZXR1cm5cbkVNLS0-PkNJOiByZXR1cm5cbkNJLT4-QzogUFVUXG5DSS0tPj5COiByZXR1cm5cbmVsc2UgY2FjaGUgaGl0XG5DLS0-PkNJOiByZXR1cm5cbkNJLS0-PkI6IHJldHVyblxuZW5kIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To deploy the application in this configuration, run the following command (from the project\u2019s root directory):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make single-instance\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now let\u2019s kick the tires! Open another shell (because the one in which you ran the previous command should now be occupied).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note that if we clear the cache and then make 98 requests for the same book, 97 requests are served by the cache<sup data-fn=\"cd0b5415-52e4-4ad6-baf4-6197efd4bd4a\" class=\"fn\"><a id=\"cd0b5415-52e4-4ad6-baf4-6197efd4bd4a-link\" href=\"#cd0b5415-52e4-4ad6-baf4-6197efd4bd4a\">2<\/a><\/sup>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make clear-cache-for-all-replicas get-book\n<strong>for <\/strong>i <strong>in<\/strong> {1..5};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/clear;\n<strong>done\nfor <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n      1 {\"host\":\"72fca0a378f9\",\"cached\":false,\"title\":\"On Lisp\"}\n     97 {\"host\":\"72fca0a378f9\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then, if we (without clearing caches) \u201cbad\u201d update the title and then make 99 requests for the book\u2026 all 99 requests are served by the cache, and all 99 have the&nbsp;<strong><em>wrong<\/em><\/strong>&nbsp;(old) title:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make bad-update-title get-book \ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/badUpdateTitle\/\"HELLO%20WORLD\"%20BAD | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BAD\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"72fca0a378f9\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     98 {\"host\":\"72fca0a378f9\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The lesson to be learned here is that you are responsible for keeping the cache up to date with the \u201csource of truth.\u201d In our example, the \u201csource of truth\u201d is our database.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next, if we (again, without clearing caches) \u201cbetter\u201d update the title and then make 98 requests for the book\u2026 97 requests are served by the cache, and one is not. However all 98 have the&nbsp;<strong><em>correct<\/em><\/strong>&nbsp;(new) title:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make better-update-title get-book \ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/betterUpdateTitle\/\"HELLO%20WORLD\"%20BETTER | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BETTER\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"72fca0a378f9\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n      1 {\"host\":\"72fca0a378f9\",\"cached\":false,\"title\":\"HELLO WORLD BETTER\"}\n     97 {\"host\":\"72fca0a378f9\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>This<\/em><\/strong>&nbsp;is a much better example of how to use&nbsp;<code>@CacheEvict<\/code>&nbsp;(in my opinion) than I was able to find online. Any time we update a record in the database, we evict any instances of it in the cache. This is much better than evicting the entire cache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, if we (again, without clearing caches) \u201cbest\u201d update the title and then make 99 requests for the book\u2026 all 99 requests are served by the cache, and all 99 have the&nbsp;<strong><em>correct<\/em><\/strong>&nbsp;(new) title:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make best-update-title get-book \ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/bestUpdateTitle\/\"HELLO%20WORLD\"%20BEST | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BEST\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"72fca0a378f9\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     98 {\"host\":\"72fca0a378f9\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This example is arguably the best method for keeping a cache up to date. Any time a record is updated in the database, we add it to the cache (if it wasn\u2019t there already) or update it in the cache (if it was already there). This allows us to serve as many lookup requests from the cache as possible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to keep playing, you can use the following command to restore the state to what it was before we started. However, if you plan to just move on to the examples in the next session, there is no need \u2013 because we are going to bring down the entire application and deploy it in a new configuration.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make restore-title-for-all-replicas clear-cache-for-all-replicas\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"replicas-with-individual-caches-shared-database\">Replicas with Individual Caches, Shared Database<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A misguided next step in the evolution of this application\u2019s architecture (especially if you think the way I do) would be to create multiple replicas of the app that all store their data in a single database \u2013&nbsp;<strong><em>without<\/em><\/strong>&nbsp;also centralizing the cache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For Spring novices, this is where a huge \u201cgotcha\u201d lies; you are always free to put a <code>@Cacheable<\/code>&nbsp;annotation on any component method. If you take no other action than that, you are using Spring\u2019s default cache implementation which is an in-memory cache. This was fine for our single replica example, however, it will cause trouble here.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/mermaid.ink\/svg\/eyJjb2RlIjoiYmxvY2stYmV0YVxuJSUgY29sdW1ucyBhdXRvIChkZWZhdWx0KVxuJSUtXG5ibG9jazpjbGllbnRibG9jazoxXG5jb2x1bW5zIDFcbnNwYWNlXG5zcGFjZVxuY2xpZW50W1wiY2xpZW50XCJdXG5zcGFjZVxuc3BhY2VcbmVuZFxuJSUtXG5zdHlsZSBjbGllbnRibG9jayBmaWxsOiNmY2UxYzUsIHN0cm9rZTogI2ZjZTFjNVxuJSUtXG5ibG9jazpsYmJsb2NrOjFcbmNvbHVtbnMgMVxuc3BhY2VcbnNwYWNlXG5sYltcImxvYWRcXG5iYWxhbmNlclwiXToxXG5zcGFjZVxuc3BhY2VcbmVuZFxuJSUtXG5zdHlsZSBsYmJsb2NrICBmaWxsOiNmY2UxYzUsIHN0cm9rZTogI2ZjZTFjNVxuJSUtXG5ibG9jazpyZXBsaWNhczoxXG5jb2x1bW5zIDJcbiUlLVxuYmxvY2s6cmVwbGljYTE6MlxuYXBwMShbXCJhcHBcIl0pIGNhY2hlMVsoXCJjYWNoZVxcbihpbiBtZW0pXCIpXVxuYXBwMSAtLT4gY2FjaGUxXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTI6MlxuYXBwMihbXCJhcHBcIl0pIGNhY2hlMlsoXCJjYWNoZVxcbihpbiBtZW0pXCIpXVxuYXBwMiAtLT4gY2FjaGUyXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTM6MlxuYXBwMyhbXCJhcHBcIl0pIGNhY2hlM1soXCJjYWNoZVxcbihpbiBtZW0pXCIpXVxuYXBwMyAtLT4gY2FjaGUzXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTQ6MlxuYXBwNChbXCJhcHBcIl0pIGNhY2hlNFsoXCJjYWNoZVxcbihpbiBtZW0pXCIpXVxuYXBwNCAtLT4gY2FjaGU0XG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTU6MlxuYXBwNShbXCJhcHBcIl0pIGNhY2hlNVsoXCJjYWNoZVxcbihpbiBtZW0pXCIpXVxuYXBwNSAtLT4gY2FjaGU1XG5lbmRcbiUlLVxuZW5kXG4lJS1cbmNsaWVudCAtLT4gbGJcbmxiIC0tPiBhcHAxXG5sYiAtLT4gYXBwMlxubGIgLS0-IGFwcDNcbmxiIC0tPiBhcHA0XG5sYiAtLT4gYXBwNVxuJSUtXG5kYlsoXCJkYlxcbihQb3N0Z3JlcylcIildOjFcbiUlLVxuY2FjaGUxIC0tPiBkYlxuY2FjaGUyIC0tPiBkYlxuY2FjaGUzIC0tPiBkYlxuY2FjaGU0IC0tPiBkYlxuY2FjaGU1IC0tPiBkYiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see from the following sequence diagram, caches still belong to individual replicas, and are updated independently of one another. If a replica answers a given retrieval request, only its cache is populated with the value. If a given replica services an update request, only its cache is updated with the new value. Any other replica that has cached the value previously will now return stale data for lookup requests!<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/mermaid.ink\/svg\/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG5wYXJ0aWNpcGFudCBCIGFzIENsaWVudFxuYm94IHJnYmEoOTgsIDE3NSwgMTkyLCAwLjUpIFJlcGxpY2EgMVxucGFydGljaXBhbnQgQ0kgYXMgQ2FjaGUgSW50ZXJjZXB0b3JcbnBhcnRpY2lwYW50IEVNIGFzIEVuZHBvaW50IE1ldGhvZFxucGFydGljaXBhbnQgQyBhcyBDYWNoZVxuZW5kXG5ib3ggcmdiYSg5OCwgMTc1LCAxOTIsIDAuNSkgUmVwbGljYSAyXG5wYXJ0aWNpcGFudCBDSTIgYXMgQ2FjaGUgSW50ZXJjZXB0b3JcbnBhcnRpY2lwYW50IEVNMiBhcyBFbmRwb2ludCBNZXRob2RcbnBhcnRpY2lwYW50IEMyIGFzIENhY2hlXG5lbmRcbmJveCByZ2JhKDk4LCAxNzUsIDE5MiwgMC41KSBEYXRhYmFzZSBTZXJ2ZXJcbnBhcnRpY2lwYW50IERCIGFzIERhdGFiYXNlXG5lbmRcbkItPj5DSTogR0VUXG5DSS0-PkM6IEdFVFxuYWx0IGNhY2hlIG1pc3NcbkNJLT4-RU06IEdFVFxuRU0tPj5EQjpTRUxFQ1RcbkRCLS0-PkVNOiByZXR1cm5cbkVNLS0-PkNJOiByZXR1cm5cbkNJLT4-QzogUFVUXG5DSS0tPj5COiByZXR1cm5cbmVsc2UgY2FjaGUgaGl0XG5DLS0-PkNJOiByZXR1cm5cbkNJLS0-PkI6IHJldHVyblxuZW5kIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Okay, let\u2019s give this new deployment scenario a spin. First things first. In a terminal where you have a prompt (i.e., not the one currently streaming docker logs from the previous scenario), run this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make clean\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Give it a little time, then check your shell that had the docker logs. All containers should stop, and you should eventually be presented with a prompt again. At that prompt, type:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make replicas-individual-cache-shared-db\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, if we clear the cache and then make 98 requests for the same book, we\u2019ll see that each replica responds with a single un-cached record, and then responds to successive requests from its own cache.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make clear-cache-for-all-replicas get-book\n<strong>for <\/strong>i <strong>in<\/strong> {1..5};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/clear;\n<strong>done\nfor <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n      1 {\"host\":\"1de0e07a1854\",\"cached\":false,\"title\":\"On Lisp\"}\n     18 {\"host\":\"1de0e07a1854\",\"cached\":true,\"title\":\"On Lisp\"}\n      1 {\"host\":\"64434b3e44f2\",\"cached\":false,\"title\":\"On Lisp\"}\n     18 {\"host\":\"64434b3e44f2\",\"cached\":true,\"title\":\"On Lisp\"}\n      1 {\"host\":\"a7c9cadc9353\",\"cached\":false,\"title\":\"On Lisp\"}\n     19 {\"host\":\"a7c9cadc9353\",\"cached\":true,\"title\":\"On Lisp\"}\n      1 {\"host\":\"e4a74ccf237b\",\"cached\":false,\"title\":\"On Lisp\"}\n     19 {\"host\":\"e4a74ccf237b\",\"cached\":true,\"title\":\"On Lisp\"}\n      1 {\"host\":\"e5910004e389\",\"cached\":false,\"title\":\"On Lisp\"}\n     19 {\"host\":\"e5910004e389\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keep in mind that all replicas now have the book we are querying for cached. If we \u201cbad\u201d update the title, successive lookups will all be served from replicas\u2019 caches. However, none of those caches will contain the proper, updated book.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make bad-update-title get-book\ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/badUpdateTitle\/\"HELLO%20WORLD\"%20BAD | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BAD\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"1de0e07a1854\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     19 {\"host\":\"1de0e07a1854\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"64434b3e44f2\",\"cached\":true,\"title\":\"On Lisp\"}\n     19 {\"host\":\"a7c9cadc9353\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"e4a74ccf237b\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"e5910004e389\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">But, when we \u201cbetter\u201d update the title, only&nbsp;<strong><em>ONE<\/em><\/strong>&nbsp;stale cached value is removed (the one in the cache of the replica that serviced the update request). When we fetch books after that update, only the cache from which the stale value was removed bothers to repopulate its cache.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make better-update-title get-book\ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/betterUpdateTitle\/\"HELLO%20WORLD\"%20BETTER | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BETTER\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"a7c9cadc9353\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     20 {\"host\":\"1de0e07a1854\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"64434b3e44f2\",\"cached\":true,\"title\":\"On Lisp\"}\n      1 {\"host\":\"a7c9cadc9353\",\"cached\":false,\"title\":\"HELLO WORLD BETTER\"}\n     18 {\"host\":\"a7c9cadc9353\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n     19 {\"host\":\"e4a74ccf237b\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"e5910004e389\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, the only thing that our \u201cbest\u201d update improves is that it prevents a cache-miss from a single replica. It has no effect on data integrity. As you can see below, we now have&nbsp;<strong><em>TWO<\/em><\/strong>&nbsp;distinct stale values distributed across our replicas\u2019 caches.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make best-update-title get-book\ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/bestUpdateTitle\/\"HELLO%20WORLD\"%20BEST | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BEST\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"e4a74ccf237b\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     20 {\"host\":\"1de0e07a1854\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"64434b3e44f2\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"a7c9cadc9353\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n     19 {\"host\":\"e4a74ccf237b\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n     19 {\"host\":\"e5910004e389\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Okay, that was fun. Remember, if you want to keep messing around with this deployment scenario, you can run the following command to \u201creset.\u201d Otherwise, let\u2019s move on to the next deployment scenario.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make restore-title-for-all-replicas clear-cache-for-all-replicas\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"replicas-with-shared-caches-shared-database\">Replicas with Shared Caches, Shared Database<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Okay, if it wasn\u2019t immediately obvious that the previous scenario was a bad idea, it should definitely be obvious in hindsight. This scenario fixes the issues of the previous scenario by also using a centralized cache.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/mermaid.ink\/svg\/eyJjb2RlIjoiYmxvY2stYmV0YVxuJSUgY29sdW1ucyBhdXRvIChkZWZhdWx0KVxuJSUtXG5ibG9jazpjbGllbnRibG9jazoxXG5jb2x1bW5zIDFcbnNwYWNlXG5zcGFjZVxuY2xpZW50W1wiY2xpZW50XCJdXG5zcGFjZVxuc3BhY2VcbmVuZFxuJSUtXG5zdHlsZSBjbGllbnRibG9jayBmaWxsOiNmY2UxYzUsIHN0cm9rZTogI2ZjZTFjNVxuJSUtXG5ibG9jazpsYmJsb2NrOjFcbmNvbHVtbnMgMVxuc3BhY2VcbnNwYWNlXG5sYltcImxvYWRcXG5iYWxhbmNlclwiXToxXG5zcGFjZVxuc3BhY2VcbmVuZFxuJSUtXG5zdHlsZSBsYmJsb2NrICBmaWxsOiNmY2UxYzUsIHN0cm9rZTogI2ZjZTFjNVxuJSUtXG5ibG9jazpyZXBsaWNhczoxXG5jb2x1bW5zIDFcbiUlLVxuYmxvY2s6cmVwbGljYTE6MVxuYXBwMShbXCJhcHBcIl0pXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTI6MVxuYXBwMihbXCJhcHBcIl0pXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTM6MVxuYXBwMyhbXCJhcHBcIl0pXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTQ6MVxuYXBwNChbXCJhcHBcIl0pXG5lbmRcbiUlLVxuYmxvY2s6cmVwbGljYTU6MVxuYXBwNShbXCJhcHBcIl0pXG5lbmRcbiUlLVxuZW5kXG4lJS1cbmNsaWVudCAtLT4gbGJcbmxiIC0tPiBhcHAxXG5sYiAtLT4gYXBwMlxubGIgLS0-IGFwcDNcbmxiIC0tPiBhcHA0XG5sYiAtLT4gYXBwNVxuJSUtXG5hcHAxIC0tPiBjYWNoZVxuYXBwMiAtLT4gY2FjaGVcbmFwcDMgLS0-IGNhY2hlXG5hcHA0IC0tPiBjYWNoZVxuYXBwNSAtLT4gY2FjaGVcbiUlLVxuY2FjaGVbKFwiY2FjaGVcXG4oUmVkaXMpXCIpXToxXG4lJS1cbmRiWyhcImRiXFxuKFBvc3RncmVzKVwiKV06MVxuJSUtXG5jYWNoZSAtLT4gZGIiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9fQ\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see from the following sequence diagram, in this scenario all replicas read from and write to the same cache. Any update to the cache is seen by all replicas<sup data-fn=\"4cb01184-ac2c-41b3-95dc-ebb444bba7fb\" class=\"fn\"><a href=\"#4cb01184-ac2c-41b3-95dc-ebb444bba7fb\" id=\"4cb01184-ac2c-41b3-95dc-ebb444bba7fb-link\">3<\/a><\/sup>.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/mermaid.ink\/svg\/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG5wYXJ0aWNpcGFudCBCIGFzIENsaWVudFxuYm94IHJnYmEoOTgsIDE3NSwgMTkyLCAwLjUpIFJlcGxpY2EgMVxucGFydGljaXBhbnQgQ0kgYXMgQ2FjaGUgSW50ZXJjZXB0b3JcbnBhcnRpY2lwYW50IEVNIGFzIEVuZHBvaW50IE1ldGhvZFxuZW5kXG5ib3ggcmdiYSg5OCwgMTc1LCAxOTIsIDAuNSkgUmVwbGljYSAyXG5wYXJ0aWNpcGFudCBDSTIgYXMgQ2FjaGUgSW50ZXJjZXB0b3JcbnBhcnRpY2lwYW50IEVNMiBhcyBFbmRwb2ludCBNZXRob2RcbmVuZFxuYm94IHJnYmEoOTgsIDE3NSwgMTkyLCAwLjUpIENhY2hlIFNlcnZlclxucGFydGljaXBhbnQgQyBhcyBDYWNoZVxuZW5kXG5ib3ggcmdiYSg5OCwgMTc1LCAxOTIsIDAuNSkgRGF0YWJhc2UgU2VydmVyXG5wYXJ0aWNpcGFudCBEQiBhcyBEYXRhYmFzZVxuZW5kXG5CLT4-Q0k6IEdFVFxuQ0ktPj5DOiBHRVRcbmFsdCBjYWNoZSBtaXNzXG5DSS0-PkVNOiBHRVRcbkVNLT4-REI6U0VMRUNUXG5EQi0tPj5FTTogcmV0dXJuXG5FTS0tPj5DSTogcmV0dXJuXG5DSS0-PkM6IFBVVFxuQ0ktLT4-QjogcmV0dXJuXG5lbHNlIGNhY2hlIGhpdFxuQy0tPj5DSTogcmV0dXJuXG5DSS0tPj5COiByZXR1cm5cbmVuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In19\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Okay, time for our final deployment scenario. You know the drill\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a shell with a prompt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make clean\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In the shell that had logs and now (after you wait a little bit) has a prompt again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make replicas-shared-cache-shared-db\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This time, when we fetch books, ONLY ONE request isn\u2019t serviced from the cache, despite requests being distributed across five replicas.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make clear-cache-for-all-replicas get-book\n<strong>for <\/strong>i <strong>in<\/strong> {1..5};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/clear;\n<strong>done\nfor <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     19 {\"host\":\"2bb40d744e9c\",\"cached\":true,\"title\":\"On Lisp\"}\n      1 {\"host\":\"380a19db4fcd\",\"cached\":false,\"title\":\"On Lisp\"}\n     19 {\"host\":\"380a19db4fcd\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"4428dd122929\",\"cached\":true,\"title\":\"On Lisp\"}\n     19 {\"host\":\"471359f51bd4\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"b7f5bbac15b7\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\u201cBad\u201d update is, of course, still broken: all requests are serviced from the cache, however no response contains the actual book as it exists in the database.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make bad-update-title get-book\ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/badUpdateTitle\/\"HELLO%20WORLD\"%20BAD | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BAD\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"2bb40d744e9c\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     19 {\"host\":\"2bb40d744e9c\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"380a19db4fcd\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"4428dd122929\",\"cached\":true,\"title\":\"On Lisp\"}\n     20 {\"host\":\"471359f51bd4\",\"cached\":true,\"title\":\"On Lisp\"}\n     19 {\"host\":\"b7f5bbac15b7\",\"cached\":true,\"title\":\"On Lisp\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\u201cBetter\u201d update now evicts the stale book from the&nbsp;<strong><em>single<\/em><\/strong>&nbsp;cache. The first request after this update requires a database lookup, but after that,&nbsp;<strong><em>all<\/em><\/strong>&nbsp;replicas are able to service successive requests using the cache.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make better-update-title get-book\ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/betterUpdateTitle\/\"HELLO%20WORLD\"%20BETTER | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BETTER\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"b7f5bbac15b7\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n      1 {\"host\":\"2bb40d744e9c\",\"cached\":false,\"title\":\"HELLO WORLD BETTER\"}\n     19 {\"host\":\"2bb40d744e9c\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n     20 {\"host\":\"380a19db4fcd\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n     19 {\"host\":\"4428dd122929\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n     20 {\"host\":\"471359f51bd4\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n     19 {\"host\":\"b7f5bbac15b7\",\"cached\":true,\"title\":\"HELLO WORLD BETTER\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And finally, \u201cbest\u201d update updates the book without requiring any successive lookups to query the database!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make best-update-title get-book\ncurl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529\/bestUpdateTitle\/\"HELLO%20WORLD\"%20BEST | jq <strong>-c<\/strong> '.'\n{\"isbn\":\"0130305529\",\"title\":\"HELLO WORLD BEST\",\"author\":\"Paul Graham\",\"cached\":false,\"host\":\"4428dd122929\"}\n<strong>for <\/strong>i <strong>in<\/strong> {1..98};\n<strong>do\n<\/strong>curl <strong>-s<\/strong> http:\/\/localhost:8080\/books\/0130305529 | jq <strong>-c<\/strong> '. | {host, cached,title}';\n<strong>done<\/strong> \\\n| sort | uniq <strong>-c<\/strong>\n     20 {\"host\":\"2bb40d744e9c\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n     19 {\"host\":\"380a19db4fcd\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n     19 {\"host\":\"4428dd122929\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n     20 {\"host\":\"471359f51bd4\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n     20 {\"host\":\"b7f5bbac15b7\",\"cached\":true,\"title\":\"HELLO WORLD BEST\"}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Again, if you want to keep playing around, you can reset the application state with this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2717 make restore-title-for-all-replicas clear-cache-for-all-replicas\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"summary\">Summary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The big takeaway here is this: Spring makes easy things easy. To that end, it has a default cache implementation. If you\u2019re not careful, you may be inclined to mark a component method as&nbsp;<code>@Cacheable<\/code>&nbsp;\u2013 and then get on with your life. This could have severe ramifications depending on how your app is deployed now or in the future.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Like I hinted at earlier, I had a lot of fun writing this sample app. The actual \u201cbook database web service\u201d was the boring part. Using AspectJ to decorate returned entities was really fun, and I think it made the examples much easier to read (you can tell from the payload which host it came from and whether or not it was cached). It was also a lot of fun using Docker Compose paired with Spring profiles to create wildly different deployment scenarios without having to change a single line of Java code. Seriously, have a look at the source \u2013 there\u2019s not a lot of it. The only bit that I would say is perhaps \u201cless than readable\u201d is the AspectJ part \u2013 there\u2019s not a lot to it, but it got complicated.<\/p>\n\n\n<ol class=\"wp-block-footnotes\"><li id=\"a5329585-69f8-4b9f-8c58-57b3d7f99bf0\">Seriously, I&#8217;m not picking on Baeldung. It&#8217;s one of my favorite websites. Even though I didn&#8217;t love its examples in this instance, the article I referenced served as one of the starting points for this very blog post. <a href=\"#a5329585-69f8-4b9f-8c58-57b3d7f99bf0-link\" aria-label=\"Jump to footnote reference 1\">\u21a9\ufe0e<\/a><\/li><li id=\"cd0b5415-52e4-4ad6-baf4-6197efd4bd4a\">The Makefile contains targets that allow me to run commands to exercise the application more succinctly.\u00a0<code>make<\/code>\u00a0also has the wonderful feature of echoing commands that it runs. This allows me to type a short command and copy paste the output directly into this post \u2013 showing you both the commands that were actually run and the output that they generated. <a href=\"#cd0b5415-52e4-4ad6-baf4-6197efd4bd4a-link\" aria-label=\"Jump to footnote reference 2\">\u21a9\ufe0e<\/a><\/li><li id=\"4cb01184-ac2c-41b3-95dc-ebb444bba7fb\">This raises very interesting questions like \u201cHow does Spring\u2019s Caching Interceptor handle cache updates from multiple threads?\u201d That\u2019s outside of the scope of this post, however, so I\u2019m just going to \u201ctrust Spring\u201d on this one. <a href=\"#4cb01184-ac2c-41b3-95dc-ebb444bba7fb-link\" aria-label=\"Jump to footnote reference 3\">\u21a9\ufe0e<\/a><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>Introduction I had occasion at work recently to consider using Spring\u2019s&nbsp;@Cacheable&nbsp;annotation. I searched the web for some hints on how to use it, but wasn\u2019t satisfied with any of the results. Even my favorite source1&nbsp;of bite-size Java code samples had examples that I felt were&nbsp;too&nbsp;simple. Using \u201clookup\u201d methods as examples of places where you may [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"[{\"content\":\"Seriously, I'm not picking on Baeldung. It's one of my favorite websites. Even though I didn't love its examples in this instance, the article I referenced served as one of the starting points for this very blog post.\",\"id\":\"a5329585-69f8-4b9f-8c58-57b3d7f99bf0\"},{\"content\":\"The Makefile contains targets that allow me to run commands to exercise the application more succinctly.\u00a0<code>make<\/code>\u00a0also has the wonderful feature of echoing commands that it runs. This allows me to type a short command and copy paste the output directly into this post \u2013 showing you both the commands that were actually run and the output that they generated.\",\"id\":\"cd0b5415-52e4-4ad6-baf4-6197efd4bd4a\"},{\"content\":\"This raises very interesting questions like \u201cHow does Spring\u2019s Caching Interceptor handle cache updates from multiple threads?\u201d That\u2019s outside of the scope of this post, however, so I\u2019m just going to \u201ctrust Spring\u201d on this one.\",\"id\":\"4cb01184-ac2c-41b3-95dc-ebb444bba7fb\"}]"},"categories":[1],"tags":[],"class_list":["post-6373","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Spring Declarative Caching - Solution Street Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Spring Declarative Caching - Solution Street Blog\" \/>\n<meta property=\"og:description\" content=\"Introduction I had occasion at work recently to consider using Spring\u2019s&nbsp;@Cacheable&nbsp;annotation. I searched the web for some hints on how to use it, but wasn\u2019t satisfied with any of the results. Even my favorite source1&nbsp;of bite-size Java code samples had examples that I felt were&nbsp;too&nbsp;simple. Using \u201clookup\u201d methods as examples of places where you may [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/\" \/>\n<meta property=\"og:site_name\" content=\"Solution Street Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-08-21T12:52:46+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-08-21T12:57:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png\" \/>\n<meta name=\"author\" content=\"Peggy Frankel\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Peggy Frankel\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/\"},\"author\":{\"name\":\"Peggy Frankel\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#\\\/schema\\\/person\\\/c4846451eff30e9514b534b2a2e01696\"},\"headline\":\"Spring Declarative Caching\",\"datePublished\":\"2024-08-21T12:52:46+00:00\",\"dateModified\":\"2024-08-21T12:57:28+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/\"},\"wordCount\":2391,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/08\\\/Spring-caching-title.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/\",\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/\",\"name\":\"Spring Declarative Caching - Solution Street Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/08\\\/Spring-caching-title.png\",\"datePublished\":\"2024-08-21T12:52:46+00:00\",\"dateModified\":\"2024-08-21T12:57:28+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#\\\/schema\\\/person\\\/c4846451eff30e9514b534b2a2e01696\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/08\\\/Spring-caching-title.png\",\"contentUrl\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/08\\\/Spring-caching-title.png\",\"width\":765,\"height\":438},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2024\\\/08\\\/21\\\/spring-declarative-caching\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Spring Declarative Caching\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/\",\"name\":\"Solution Street Blog\",\"description\":\"Quality Software Engineering - Technology and Consulting Articles\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#\\\/schema\\\/person\\\/c4846451eff30e9514b534b2a2e01696\",\"name\":\"Peggy Frankel\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/af71ceb16f89d32c9bb825a47f8057da9283b4a27a934bf0c47cdef65ad0eb5d?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/af71ceb16f89d32c9bb825a47f8057da9283b4a27a934bf0c47cdef65ad0eb5d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/af71ceb16f89d32c9bb825a47f8057da9283b4a27a934bf0c47cdef65ad0eb5d?s=96&d=mm&r=g\",\"caption\":\"Peggy Frankel\"},\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/author\\\/pfrankel\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Spring Declarative Caching - Solution Street Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/","og_locale":"en_US","og_type":"article","og_title":"Spring Declarative Caching - Solution Street Blog","og_description":"Introduction I had occasion at work recently to consider using Spring\u2019s&nbsp;@Cacheable&nbsp;annotation. I searched the web for some hints on how to use it, but wasn\u2019t satisfied with any of the results. Even my favorite source1&nbsp;of bite-size Java code samples had examples that I felt were&nbsp;too&nbsp;simple. Using \u201clookup\u201d methods as examples of places where you may [&hellip;]","og_url":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/","og_site_name":"Solution Street Blog","article_published_time":"2024-08-21T12:52:46+00:00","article_modified_time":"2024-08-21T12:57:28+00:00","og_image":[{"url":"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png","type":"","width":"","height":""}],"author":"Peggy Frankel","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Peggy Frankel","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#article","isPartOf":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/"},"author":{"name":"Peggy Frankel","@id":"https:\/\/www.solutionstreet.com\/blog\/#\/schema\/person\/c4846451eff30e9514b534b2a2e01696"},"headline":"Spring Declarative Caching","datePublished":"2024-08-21T12:52:46+00:00","dateModified":"2024-08-21T12:57:28+00:00","mainEntityOfPage":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/"},"wordCount":2391,"commentCount":0,"image":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#primaryimage"},"thumbnailUrl":"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/","url":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/","name":"Spring Declarative Caching - Solution Street Blog","isPartOf":{"@id":"https:\/\/www.solutionstreet.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#primaryimage"},"image":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#primaryimage"},"thumbnailUrl":"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png","datePublished":"2024-08-21T12:52:46+00:00","dateModified":"2024-08-21T12:57:28+00:00","author":{"@id":"https:\/\/www.solutionstreet.com\/blog\/#\/schema\/person\/c4846451eff30e9514b534b2a2e01696"},"breadcrumb":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#primaryimage","url":"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png","contentUrl":"https:\/\/www.solutionstreet.com\/blog\/wp-content\/uploads\/2024\/08\/Spring-caching-title.png","width":765,"height":438},{"@type":"BreadcrumbList","@id":"https:\/\/www.solutionstreet.com\/blog\/2024\/08\/21\/spring-declarative-caching\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.solutionstreet.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Spring Declarative Caching"}]},{"@type":"WebSite","@id":"https:\/\/www.solutionstreet.com\/blog\/#website","url":"https:\/\/www.solutionstreet.com\/blog\/","name":"Solution Street Blog","description":"Quality Software Engineering - Technology and Consulting Articles","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.solutionstreet.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.solutionstreet.com\/blog\/#\/schema\/person\/c4846451eff30e9514b534b2a2e01696","name":"Peggy Frankel","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/af71ceb16f89d32c9bb825a47f8057da9283b4a27a934bf0c47cdef65ad0eb5d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/af71ceb16f89d32c9bb825a47f8057da9283b4a27a934bf0c47cdef65ad0eb5d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/af71ceb16f89d32c9bb825a47f8057da9283b4a27a934bf0c47cdef65ad0eb5d?s=96&d=mm&r=g","caption":"Peggy Frankel"},"url":"https:\/\/www.solutionstreet.com\/blog\/author\/pfrankel\/"}]}},"_links":{"self":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts\/6373","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/comments?post=6373"}],"version-history":[{"count":48,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts\/6373\/revisions"}],"predecessor-version":[{"id":6426,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts\/6373\/revisions\/6426"}],"wp:attachment":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/media?parent=6373"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/categories?post=6373"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/tags?post=6373"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}