{"id":1936,"date":"2021-01-20T01:43:07","date_gmt":"2021-01-19T20:13:07","guid":{"rendered":"https:\/\/judepereira.com\/blog\/?p=1936"},"modified":"2021-01-20T01:43:08","modified_gmt":"2021-01-19T20:13:08","slug":"taming-a-throttled-api-with-dynamic-proxies-in-java","status":"publish","type":"post","link":"https:\/\/judepereira.com\/blog\/taming-a-throttled-api-with-dynamic-proxies-in-java\/","title":{"rendered":"Taming a throttled API with Dynamic Proxies in Java"},"content":{"rendered":"\n<p>Recently, at <a href=\"http:\/\/clevertap.com\" target=\"_blank\" rel=\"noreferrer noopener\">CleverTap<\/a>, we&#8217;ve begun migrating some of our largest clusters to a new protocol (for starters, think ~115 instances at a time). One of the most fun things I&#8217;ve had my hands on during this migration was the AWS Systems Manager API.<\/p>\n\n\n\n<p>When we scaled up our migrations gradually from a 10 node cluster, we were challenged with dealing with API throttling exceptions (because sure, who wouldn&#8217;t throttle their APIs?). There were two immediate solutions that hit our mind:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Review every usage of the SSM client and handle the throttling exception gracefully<\/li><li>Wrap the SSM client and handle the throttling exception transparently<\/li><\/ol>\n\n\n\n<p>Naturally, we settled for option 2. I am a big fan of hidden abstractions. So what did we do? We implemented the AWS interface in question, only to discover that we&#8217;d have to handle a ton of methods individually (obviously copy\/paste). There had to be a better solution!<\/p>\n\n\n\n<p>And then, Google did it&#8217;s thing. We discovered <a href=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/technotes\/guides\/reflection\/proxy.html\" target=\"_blank\" rel=\"noreferrer noopener\" title=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/technotes\/guides\/reflection\/proxy.html\"><strong>Dynamic Proxies<\/strong><\/a>. And viola! We were able to transparently handle and implement an auto retry strategy within <strong>just 14 lines<\/strong>!<\/p>\n\n\n\n<p>Here&#8217;s what it looked like:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler\"><div class=\"wp-block-embed__wrapper\">\n<style>.gist table { margin-bottom: 0; }<\/style><div style=\"tab-size: 8\" id=\"gist107466873\" class=\"gist\">\n    <div class=\"gist-file\" translate=\"no\" data-color-mode=\"light\" data-light-theme=\"light\">\n      <div class=\"gist-data\">\n        \n<div class=\"js-gist-file-update-container js-task-list-container\">\n      <div id=\"file-tameathrottledapi-java\" class=\"file my-2\">\n    \n    <div itemprop=\"text\"\n      class=\"Box-body p-0 blob-wrapper data type-java  \"\n      style=\"overflow: auto\" tabindex=\"0\" role=\"region\"\n      aria-label=\"TameAThrottledAPI.java content, created by judepereira on 08:05PM on January 19, 2021.\"\n    >\n\n        \n<div class=\"js-check-hidden-unicode js-blob-code-container blob-code-content\">\n\n  <template class=\"js-file-alert-template\">\n  <div data-view-component=\"true\" class=\"flash flash-warn flash-full d-flex flex-items-center\">\n  <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-alert\">\n    <path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"><\/path>\n<\/svg>\n    <span>\n      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\"Link--inTextBlock\" href=\"https:\/\/github.co\/hiddenchars\" target=\"_blank\">Learn more about bidirectional Unicode characters<\/a>\n    <\/span>\n\n\n  <div data-view-component=\"true\" class=\"flash-action\">        <a href=\"{{ revealButtonHref }}\" data-view-component=\"true\" class=\"btn-sm btn\">    Show hidden characters\n<\/a>\n<\/div>\n<\/div><\/template>\n<template class=\"js-line-alert-template\">\n  <span aria-label=\"This line has hidden Unicode characters\" data-view-component=\"true\" class=\"line-alert tooltipped tooltipped-e\">\n    <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-alert\">\n    <path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"><\/path>\n<\/svg>\n<\/span><\/template>\n\n  <table data-hpc class=\"highlight tab-size js-file-line-container\" data-tab-size=\"4\" data-paste-markdown-skip data-tagsearch-path=\"TameAThrottledAPI.java\">\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L1\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"1\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC1\" class=\"blob-code blob-code-inner js-file-line\">MyStubbornAPIInterface actualInstance = &#8230; \/\/ Create it however you&#39;d create your original instance.<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L2\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"2\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC2\" class=\"blob-code blob-code-inner js-file-line\">MyStubbornAPIInterface proxiedInstance = (MyStubbornAPIInterface) Proxy.newProxyInstance(actualInstance.getClass().getClassLoader(),<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L3\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"3\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC3\" class=\"blob-code blob-code-inner js-file-line\">        new Class[]{MyStubbornAPIInterface.class}, (proxy, method, args) -&gt; {<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L4\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"4\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC4\" class=\"blob-code blob-code-inner js-file-line\">            while (true) {<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L5\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"5\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC5\" class=\"blob-code blob-code-inner js-file-line\">                try {<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L6\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"6\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC6\" class=\"blob-code blob-code-inner js-file-line\">                    return method.invoke(actualInstance, args);<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L7\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"7\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC7\" class=\"blob-code blob-code-inner js-file-line\">                } catch (MyThrottlingException e) {<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L8\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"8\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC8\" class=\"blob-code blob-code-inner js-file-line\">                    try {<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L9\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"9\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC9\" class=\"blob-code blob-code-inner js-file-line\">                        Thread.sleep(ThreadLocalRandom.current().nextInt(1, 5) * 1000L);<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L10\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"10\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC10\" class=\"blob-code blob-code-inner js-file-line\">                    } catch (InterruptedException e) {<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L11\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"11\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC11\" class=\"blob-code blob-code-inner js-file-line\">                        Thread.currentThread().interrupt();<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L12\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"12\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC12\" class=\"blob-code blob-code-inner js-file-line\">                    }<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L13\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"13\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC13\" class=\"blob-code blob-code-inner js-file-line\">                }<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L14\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"14\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC14\" class=\"blob-code blob-code-inner js-file-line\">            }<\/td>\n        <\/tr>\n        <tr>\n          <td id=\"file-tameathrottledapi-java-L15\" class=\"blob-num js-line-number js-blob-rnum\" data-line-number=\"15\"><\/td>\n          <td id=\"file-tameathrottledapi-java-LC15\" class=\"blob-code blob-code-inner js-file-line\">        });<\/td>\n        <\/tr>\n  <\/table>\n<\/div>\n\n\n    <\/div>\n\n  <\/div>\n\n<\/div>\n\n      <\/div>\n      <div class=\"gist-meta\">\n        <a href=\"https:\/\/gist.github.com\/judepereira\/3d4c7c0189bc84ca922b703a7e5feff1\/raw\/9bb63ab9d00c259d87bc48790fbce826351a6032\/TameAThrottledAPI.java\" style=\"float:right\" class=\"Link--inTextBlock\">view raw<\/a>\n        <a href=\"https:\/\/gist.github.com\/judepereira\/3d4c7c0189bc84ca922b703a7e5feff1#file-tameathrottledapi-java\" class=\"Link--inTextBlock\">\n          TameAThrottledAPI.java\n        <\/a>\n        hosted with &#10084; by <a class=\"Link--inTextBlock\" href=\"https:\/\/github.com\">GitHub<\/a>\n      <\/div>\n    <\/div>\n<\/div>\n\n<\/div><\/figure>\n\n\n\n<p>The code above can be easily adapted to various SDKs (in our case, it was the AWS SDK).<\/p>\n\n\n\n<p>Now, all we had to do was pass around this proxied instance, and viola, the consumers of this API had no clue that the API implemented an auto retry mechanism!<\/p>\n\n\n\n<p>Cheers!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, at CleverTap, we&#8217;ve begun migrating some of our largest clusters to a new protocol (for starters, think ~115 instances at a time). One of the most fun things I&#8217;ve had my hands on during this migration was the AWS Systems Manager API. When we scaled up our migrations gradually from a 10 node cluster, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[52],"tags":[662,65,660,659,463,658,462,663,661,664],"class_list":["post-1936","post","type-post","status-publish","format-standard","hentry","category-code","tag-api","tag-dynamic","tag-invocation","tag-invocation-handler","tag-java","tag-proxies","tag-reflection","tag-sdks","tag-stubborn","tag-throttling"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pqtyx-ve","jetpack-related-posts":[{"id":1675,"url":"https:\/\/judepereira.com\/blog\/sending-notifications-via-apples-new-http2-api-using-jetty-9-3-6\/","url_meta":{"origin":1936,"position":0},"title":"Sending notifications via Apple&#8217;s new HTTP\/2 API (using Jetty 9.3.6)","author":"Jude Pereira","date":"January 9, 2016","format":false,"excerpt":"HTTP\/2 is still very much new to Java, and as such, there are just two libraries who support it - Jetty (from 9.3), and Netty (in alpha). If you're going the Jetty way (as I have), you'll need to add their ALPN library to your boot classpath. Note: Jetty 9.3.x\u2026","rel":"","context":"In &quot;another snippet | code&quot;","block_context":{"text":"another snippet | code","link":"https:\/\/judepereira.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1696,"url":"https:\/\/judepereira.com\/blog\/mocking-the-http2-apple-push-notification-gateway-in-go\/","url_meta":{"origin":1936,"position":1},"title":"Mocking the HTTP\/2 Apple push notification gateway in Go","author":"Jude Pereira","date":"February 17, 2016","format":false,"excerpt":"At CleverTap, we had the need to mock the new HTTP\/2 Apple push gateway API. After trying to do this in Java, and then in Python, I succeeded in getting it to work in Go. Go out of all languages! To me, Go has a completely strange syntax, compared to\u2026","rel":"","context":"In &quot;another snippet | code&quot;","block_context":{"text":"another snippet | code","link":"https:\/\/judepereira.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1824,"url":"https:\/\/judepereira.com\/blog\/java-9s-httpclient-doesnt-allow-custom-http-2-authorization-headers\/","url_meta":{"origin":1936,"position":2},"title":"Java 9&#8217;s HttpClient doesn&#8217;t allow custom HTTP\/2 authorization headers","author":"Jude Pereira","date":"January 23, 2018","format":false,"excerpt":"TL;DR Java 9 HttpClient does not allow custom Authorization header unless you resort to a hack. Read more about my blog post on CleverTap's blog.","rel":"","context":"In &quot;miscellaneous&quot;","block_context":{"text":"miscellaneous","link":"https:\/\/judepereira.com\/blog\/category\/misc\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1845,"url":"https:\/\/judepereira.com\/blog\/intellij-on-steroids-with-g1-gc\/","url_meta":{"origin":1936,"position":3},"title":"IntelliJ on steroids with G1 GC","author":"Jude Pereira","date":"June 15, 2018","format":false,"excerpt":"Lately, I noticed that IntelliJ started to pause for quite some time during it's GC cycles, and that it was very frequent when I was editing three files (over 1.2k LOC each) split vertically. The current version of IntelliJ runs on a bundled version of Java 1.8, who's default garbage\u2026","rel":"","context":"In &quot;another snippet | code&quot;","block_context":{"text":"another snippet | code","link":"https:\/\/judepereira.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1616,"url":"https:\/\/judepereira.com\/blog\/0f-float-min_value-false\/","url_meta":{"origin":1936,"position":4},"title":"0f > Float.MIN_VALUE = false!","author":"Jude Pereira","date":"April 23, 2015","format":false,"excerpt":"Updates: Turns out that this is the expected behaviour from the java doc: A constant holding the smallest positive nonzero value of type float, 2-149. So now, how do I get the smallest negative value that a float can hold? Just came across the most weirdest thing ever in Java\u2026","rel":"","context":"In &quot;miscellaneous&quot;","block_context":{"text":"miscellaneous","link":"https:\/\/judepereira.com\/blog\/category\/misc\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1969,"url":"https:\/\/judepereira.com\/blog\/cloudflare-zero-trust-gateway-and-net-neutrality\/","url_meta":{"origin":1936,"position":5},"title":"Cloudflare Zero Trust Gateway and Net Neutrality","author":"Jude Pereira","date":"April 5, 2023","format":false,"excerpt":"Is Cloudflare ruining the entire concept of a distributed internet? Is it on a path to violate Net Neutrality? What can you do to prevent this?","rel":"","context":"In &quot;miscellaneous&quot;","block_context":{"text":"miscellaneous","link":"https:\/\/judepereira.com\/blog\/category\/misc\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screenshot-2023-04-04-at-20.01.01.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screenshot-2023-04-04-at-20.01.01.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screenshot-2023-04-04-at-20.01.01.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screenshot-2023-04-04-at-20.01.01.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screenshot-2023-04-04-at-20.01.01.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screenshot-2023-04-04-at-20.01.01.png?resize=1400%2C800&ssl=1 4x"},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts\/1936","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/comments?post=1936"}],"version-history":[{"count":3,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts\/1936\/revisions"}],"predecessor-version":[{"id":1939,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts\/1936\/revisions\/1939"}],"wp:attachment":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/media?parent=1936"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/categories?post=1936"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/tags?post=1936"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}